elio 1.1.0

Snappy, batteries-included terminal file manager with rich previews, inline images, bulk actions, and trash support.
Documentation
use super::{
    PDF_PAGE_MIN, PDF_PROBE_PREFETCH_AHEAD_DISTANCE, PDF_PROBE_PREFETCH_BEHIND_DISTANCE,
    PDF_RENDER_PREFETCH_AHEAD_DISTANCE, PDF_RENDER_PREFETCH_BEHIND_DISTANCE, PdfOverlayRequest,
    PdfPageKey, PdfRenderKey,
};
use crate::app::{App, jobs};

impl App {
    pub(super) fn refresh_pdf_prefetch_window(&mut self) {
        let Some(session) = self.preview.pdf.session.as_ref() else {
            self.clear_pending_pdf_work();
            return;
        };
        let path = session.path.clone();
        let size = session.size;
        let modified = session.modified;

        let window_pages = self.pdf_probe_window_pages();
        self.jobs
            .scheduler
            .retain_pdf_probe_pages(&path, size, modified, &window_pages);
        self.preview.pdf.pending_page_probes.retain(|key| {
            key.path == path
                && key.size == size
                && key.modified == modified
                && window_pages.contains(&key.page)
        });

        let render_variants = self.desired_pdf_render_variants();
        self.jobs
            .scheduler
            .retain_pdf_render_variants(&path, size, modified, &render_variants);
        self.preview.pdf.pending_renders.retain(|key| {
            key.path == path
                && key.size == size
                && key.modified == modified
                && render_variants.contains(&(key.page, key.width_px, key.height_px))
        });

        self.queue_current_pdf_probe();
        self.queue_current_pdf_render();
        self.queue_prefetch_pdf_probes();
        if self.current_pdf_probe_ready() {
            self.queue_prefetch_pdf_renders();
        }
    }

    fn queue_prefetch_pdf_probes(&mut self) {
        for page in self.pdf_prefetch_probe_pages() {
            self.try_queue_pdf_probe_page(page);
        }
    }

    fn queue_prefetch_pdf_renders(&mut self) {
        for page in self.pdf_prefetch_render_pages() {
            self.try_queue_pdf_render_page(page);
        }
    }

    fn try_queue_pdf_probe_page(&mut self, page: usize) {
        let Some(session) = &self.preview.pdf.session else {
            return;
        };

        self.submit_pdf_probe_key(
            PdfPageKey {
                path: session.path.clone(),
                size: session.size,
                modified: session.modified,
                page,
            },
            jobs::PdfJobPriority::Prefetch,
        );
    }

    fn try_queue_pdf_render_page(&mut self, page: usize) {
        let Some(key) = self.pdf_render_key_for_page(page) else {
            return;
        };
        self.submit_pdf_render_key(key, jobs::PdfJobPriority::Prefetch);
    }

    fn pdf_probe_window_pages(&self) -> Vec<usize> {
        let Some(session) = self.preview.pdf.session.as_ref() else {
            return Vec::new();
        };

        let mut pages = vec![session.current_page];
        pages.extend(self.pdf_prefetch_pages(
            session.current_page,
            session.total_pages,
            PDF_PROBE_PREFETCH_AHEAD_DISTANCE,
            PDF_PROBE_PREFETCH_BEHIND_DISTANCE,
        ));
        pages
    }

    pub(super) fn pdf_prefetch_probe_pages(&self) -> Vec<usize> {
        let Some(session) = self.preview.pdf.session.as_ref() else {
            return Vec::new();
        };
        self.pdf_prefetch_pages(
            session.current_page,
            session.total_pages,
            PDF_PROBE_PREFETCH_AHEAD_DISTANCE,
            PDF_PROBE_PREFETCH_BEHIND_DISTANCE,
        )
    }

    pub(super) fn pdf_prefetch_render_pages(&self) -> Vec<usize> {
        let Some(session) = self.preview.pdf.session.as_ref() else {
            return Vec::new();
        };
        self.pdf_prefetch_pages(
            session.current_page,
            session.total_pages,
            PDF_RENDER_PREFETCH_AHEAD_DISTANCE,
            PDF_RENDER_PREFETCH_BEHIND_DISTANCE,
        )
    }

    fn current_pdf_probe_ready(&self) -> bool {
        self.active_pdf_overlay_request()
            .as_ref()
            .is_some_and(|request| {
                self.preview
                    .pdf
                    .page_dimensions
                    .contains_key(&PdfPageKey::from_request(request))
            })
    }

    pub(super) fn pdf_overlay_request_for_page(&self, page: usize) -> Option<PdfOverlayRequest> {
        if !self.terminal_image_overlay_available() {
            return None;
        }

        let session = self.preview.pdf.session.as_ref()?;
        let area = self.input.frame_state.preview_content_area?;
        if area.width == 0 || area.height == 0 {
            return None;
        }

        Some(PdfOverlayRequest {
            path: session.path.clone(),
            size: session.size,
            modified: session.modified,
            page,
            area,
        })
    }

    pub(super) fn pdf_render_key_for_page(&self, page: usize) -> Option<PdfRenderKey> {
        let request = self.pdf_overlay_request_for_page(page)?;
        let placement = self.overlay_placement_for_request(&request)?;
        Some(PdfRenderKey::from_request(&request, placement))
    }

    fn desired_pdf_render_variants(&self) -> Vec<(usize, u32, u32)> {
        let Some(session) = self.preview.pdf.session.as_ref() else {
            return Vec::new();
        };

        let mut variants = Vec::new();
        if let Some(key) = self.pdf_render_key_for_page(session.current_page) {
            variants.push((key.page, key.width_px, key.height_px));
        }
        if self.current_pdf_probe_ready() {
            for page in self.pdf_prefetch_render_pages() {
                if let Some(key) = self.pdf_render_key_for_page(page) {
                    variants.push((key.page, key.width_px, key.height_px));
                }
            }
        }
        variants
    }

    fn pdf_prefetch_pages(
        &self,
        current_page: usize,
        total_pages: Option<usize>,
        ahead_distance: usize,
        behind_distance: usize,
    ) -> Vec<usize> {
        let Some(total_pages) = total_pages else {
            return Vec::new();
        };
        let prefer_backward = self.preview.pdf.last_navigation_direction < 0;
        let mut pages = Vec::new();

        if prefer_backward {
            for distance in 1..=ahead_distance {
                let previous_page = current_page.saturating_sub(distance);
                if previous_page >= PDF_PAGE_MIN {
                    pages.push(previous_page);
                }
            }
            for distance in 1..=behind_distance {
                let next_page = current_page.saturating_add(distance);
                if next_page <= total_pages {
                    pages.push(next_page);
                }
            }
        } else {
            for distance in 1..=ahead_distance {
                let next_page = current_page.saturating_add(distance);
                if next_page <= total_pages {
                    pages.push(next_page);
                }
            }
            for distance in 1..=behind_distance {
                let previous_page = current_page.saturating_sub(distance);
                if previous_page >= PDF_PAGE_MIN {
                    pages.push(previous_page);
                }
            }
        }

        pages
    }

    fn queue_current_pdf_probe(&mut self) {
        let Some(session) = &self.preview.pdf.session else {
            return;
        };

        self.submit_pdf_probe_key(
            PdfPageKey {
                path: session.path.clone(),
                size: session.size,
                modified: session.modified,
                page: session.current_page,
            },
            jobs::PdfJobPriority::Current,
        );
    }

    fn queue_current_pdf_render(&mut self) {
        let Some(key) = self.active_pdf_render_key() else {
            return;
        };
        self.submit_pdf_render_key(key, jobs::PdfJobPriority::Current);
    }

    fn submit_pdf_probe_key(&mut self, key: PdfPageKey, priority: jobs::PdfJobPriority) {
        if self.preview.pdf.page_dimensions.contains_key(&key)
            || self.preview.pdf.pending_page_probes.contains(&key)
            || self.preview.pdf.failed_page_probes.contains(&key)
        {
            return;
        }

        if self.jobs.scheduler.submit_pdf_probe(
            jobs::PdfProbeRequest {
                path: key.path.clone(),
                size: key.size,
                modified: key.modified,
                page: key.page,
            },
            priority,
        ) {
            self.preview.pdf.pending_page_probes.insert(key);
        } else {
            self.preview.pdf.failed_page_probes.insert(key);
        }
    }

    fn submit_pdf_render_key(&mut self, key: PdfRenderKey, priority: jobs::PdfJobPriority) {
        if self.cached_render_exists(&key)
            || self.preview.pdf.pending_renders.contains(&key)
            || self.preview.pdf.failed_renders.contains(&key)
        {
            return;
        }

        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),
            },
            priority,
        ) {
            self.preview.pdf.pending_renders.insert(key);
        } else {
            self.preview.pdf.failed_renders.insert(key);
        }
    }
}