elio 1.5.1

Snappy, batteries-included terminal file manager with rich previews, inline images, bulk actions, and trash support.
Documentation
use super::geometry::fit_pdf_page;
use super::{
    FittedPdfPlacement, PDF_RENDER_CACHE_LIMIT, PdfOverlayRequest, PdfPageDimensions, PdfPageKey,
    PdfRenderKey,
};
use crate::app::overlays::inline_image::{
    ImageProtocol, RenderedImageDimensions, fit_image_area, read_png_dimensions,
};
use crate::app::{App, jobs};
use std::{
    fs,
    path::{Path, PathBuf},
};

impl App {
    pub(super) fn ensure_pdf_render(&mut self, key: &PdfRenderKey) -> Option<PathBuf> {
        if let Some(path) = self.cached_pdf_render_path(key) {
            return Some(path);
        }
        if self.preview.pdf.failed_renders.contains(key)
            || self.preview.pdf.pending_renders.contains(key)
        {
            return None;
        }
        if !self.jobs.scheduler.submit_pdf_render(
            jobs::PdfRenderRequest {
                path: key.path.clone(),
                size: key.size,
                modified: key.modified,
                page: key.page,
                width_px: key.width_px,
                height_px: key.height_px,
                sixel_prepare: self.pdf_sixel_prepare_for_render_key(key),
            },
            jobs::PdfJobPriority::Current,
        ) {
            self.preview.pdf.failed_renders.insert(key.clone());
            return None;
        }
        self.preview.pdf.pending_renders.insert(key.clone());
        None
    }

    pub(super) fn ensure_pdf_page_probe(
        &mut self,
        request: &PdfOverlayRequest,
    ) -> Option<PdfPageDimensions> {
        let key = PdfPageKey::from_request(request);
        if let Some(dimensions) = self.preview.pdf.page_dimensions.get(&key).copied() {
            return Some(dimensions);
        }
        if self.preview.pdf.failed_page_probes.contains(&key)
            || self.preview.pdf.pending_page_probes.contains(&key)
        {
            return None;
        }
        if !self.jobs.scheduler.submit_pdf_probe(
            jobs::PdfProbeRequest {
                path: request.path.clone(),
                size: request.size,
                modified: request.modified,
                page: request.page,
            },
            jobs::PdfJobPriority::Current,
        ) {
            self.preview.pdf.failed_page_probes.insert(key);
            return None;
        }
        self.preview.pdf.pending_page_probes.insert(key);
        None
    }

    pub(super) fn overlay_placement_for_request(
        &self,
        request: &PdfOverlayRequest,
    ) -> Option<FittedPdfPlacement> {
        let window_size = self.cached_terminal_window()?;
        let page_dimensions = self.cached_pdf_page_dimensions(request)?;
        Some(fit_pdf_page(request.area, window_size, page_dimensions))
    }

    fn cached_pdf_page_dimensions(&self, request: &PdfOverlayRequest) -> Option<PdfPageDimensions> {
        self.preview
            .pdf
            .page_dimensions
            .get(&PdfPageKey::from_request(request))
            .copied()
    }

    pub(super) fn cached_pdf_render_path(&mut self, key: &PdfRenderKey) -> Option<PathBuf> {
        if let Some(path) = self.preview.pdf.rendered_pages.get(key)
            && path.exists()
        {
            return Some(path.clone());
        }

        self.preview.pdf.rendered_pages.remove(key);
        self.preview.pdf.rendered_page_dimensions.remove(key);
        self.preview.pdf.render_order.retain(|queued| queued != key);
        None
    }

    pub(super) fn cached_render_exists(&self, key: &PdfRenderKey) -> bool {
        self.preview.pdf.rendered_pages.contains_key(key)
    }

    pub(super) fn remember_rendered_pdf(
        &mut self,
        key: PdfRenderKey,
        path: PathBuf,
        dimensions: Option<RenderedImageDimensions>,
    ) {
        self.preview.pdf.rendered_pages.insert(key.clone(), path);
        if let Some(dimensions) = dimensions {
            self.preview
                .pdf
                .rendered_page_dimensions
                .insert(key.clone(), dimensions);
        }
        self.preview
            .pdf
            .render_order
            .retain(|queued| queued != &key);
        self.preview.pdf.render_order.push_back(key);
        while self.preview.pdf.render_order.len() > PDF_RENDER_CACHE_LIMIT {
            if let Some(stale_key) = self.preview.pdf.render_order.pop_front()
                && let Some(stale_path) = self.preview.pdf.rendered_pages.remove(&stale_key)
            {
                self.preview.pdf.rendered_page_dimensions.remove(&stale_key);
                let _ = fs::remove_file(stale_path);
            }
        }
    }

    pub(super) fn invalidate_rendered_pdf(&mut self, key: &PdfRenderKey) {
        if let Some(path) = self.preview.pdf.rendered_pages.remove(key) {
            let _ = fs::remove_file(path);
        }
        self.preview.pdf.rendered_page_dimensions.remove(key);
        self.preview.pdf.render_order.retain(|queued| queued != key);
        self.preview.pdf.pending_renders.remove(key);
        self.preview.pdf.failed_renders.remove(key);
    }

    pub(super) fn active_pdf_render_key(&self) -> Option<PdfRenderKey> {
        let request = self.active_pdf_overlay_request()?;
        let placement = self.overlay_placement_for_request(&request)?;
        Some(self.pdf_render_key_from_request(&request, placement))
    }

    pub(super) fn resolved_pdf_display_placement(
        &mut self,
        request: &PdfOverlayRequest,
        render_key: &PdfRenderKey,
        fallback: FittedPdfPlacement,
        rendered: &Path,
    ) -> FittedPdfPlacement {
        let Some(window_size) = self.cached_terminal_window() else {
            return fallback;
        };
        let Some(image_dimensions) = self.cached_rendered_image_dimensions(render_key, rendered)
        else {
            return fallback;
        };

        FittedPdfPlacement {
            image_area: fit_image_area(
                request.area,
                window_size,
                image_dimensions.width_px as f32 / image_dimensions.height_px as f32,
            ),
            ..fallback
        }
    }

    fn cached_rendered_image_dimensions(
        &mut self,
        key: &PdfRenderKey,
        rendered: &Path,
    ) -> Option<RenderedImageDimensions> {
        if let Some(dimensions) = self.preview.pdf.rendered_page_dimensions.get(key).copied() {
            return Some(dimensions);
        }

        let dimensions = read_png_dimensions(rendered)?;
        self.preview
            .pdf
            .rendered_page_dimensions
            .insert(key.clone(), dimensions);
        Some(dimensions)
    }

    pub(super) fn cached_display_placement_for_request(
        &self,
        request: &PdfOverlayRequest,
        fallback: FittedPdfPlacement,
    ) -> Option<FittedPdfPlacement> {
        let window_size = self.cached_terminal_window()?;
        let render_key = self.pdf_render_key_from_request(request, fallback);
        let image_dimensions = self
            .preview
            .pdf
            .rendered_page_dimensions
            .get(&render_key)
            .copied()?;
        Some(FittedPdfPlacement {
            image_area: fit_image_area(
                request.area,
                window_size,
                image_dimensions.width_px as f32 / image_dimensions.height_px as f32,
            ),
            ..fallback
        })
    }

    pub(super) fn pdf_page_key_from_request(&self, request: &PdfOverlayRequest) -> PdfPageKey {
        PdfPageKey::from_request(request)
    }

    pub(super) fn pdf_render_key_from_request(
        &self,
        request: &PdfOverlayRequest,
        placement: FittedPdfPlacement,
    ) -> PdfRenderKey {
        PdfRenderKey::from_request(request, placement)
    }

    pub(super) fn pdf_sixel_prepare_for_render_key(
        &self,
        key: &PdfRenderKey,
    ) -> Option<jobs::SixelPrepareConfig> {
        if self.preview.terminal_images.protocol != ImageProtocol::Sixel {
            return None;
        }

        let window_size = self.cached_terminal_window()?;
        let request = self.pdf_overlay_request_for_page(key.page)?;
        let placement = self.overlay_placement_for_request(&request)?;
        let render_key = self.pdf_render_key_from_request(&request, placement);
        if render_key.width_px != key.width_px || render_key.height_px != key.height_px {
            return None;
        }

        Some(jobs::SixelPrepareConfig {
            area_width: placement.image_area.width,
            area_height: placement.image_area.height,
            window_size,
        })
    }
}