elio 1.5.1

Snappy, batteries-included terminal file manager with rich previews, inline images, bulk actions, and trash support.
Documentation
use super::*;
use std::{path::Path, time::Instant};

impl App {
    pub(crate) fn directory_item_count_label(&self, entry: &Entry) -> Option<String> {
        self.directory_item_count_value(entry)
            .map(format_item_count)
    }

    pub(crate) fn directory_item_count_value(&self, entry: &Entry) -> Option<usize> {
        self.directory_item_count(entry)
    }

    pub(super) fn cache_directory_item_count(
        &mut self,
        path: PathBuf,
        modified: Option<SystemTime>,
        show_hidden: bool,
        item_count: Option<usize>,
    ) {
        let key = DirectoryItemCountKey {
            path,
            modified,
            show_hidden,
        };
        self.navigation
            .directory_item_count_cache
            .insert(key.clone(), item_count);
        self.navigation
            .directory_item_count_order
            .retain(|queued| queued != &key);
        self.navigation
            .directory_item_count_order
            .push_back(key.clone());

        while self.navigation.directory_item_count_order.len() > DIRECTORY_ITEM_COUNT_CACHE_LIMIT {
            if let Some(stale_key) = self.navigation.directory_item_count_order.pop_front() {
                self.navigation
                    .directory_item_count_cache
                    .remove(&stale_key);
            }
        }
    }

    pub(super) fn queue_visible_directory_item_counts(&mut self) {
        let viewport = DirectoryCountViewport {
            fingerprint: self.navigation.directory_runtime.fingerprint,
            scroll_row: self.navigation.scroll_row,
            cols: self.input.frame_state.metrics.cols.max(1),
            rows_visible: self.input.frame_state.metrics.rows_visible.max(1),
            show_hidden: self.effective_show_hidden(),
        };
        if self.navigation.directory_count_viewport == Some(viewport) {
            return;
        }
        self.navigation.directory_count_viewport = Some(viewport);
        self.navigation.directory_item_count_ready_at =
            Some(Instant::now() + DIRECTORY_ITEM_COUNT_IDLE_DELAY);
    }

    pub(crate) fn process_directory_item_count_timer(&mut self) -> bool {
        let Some(deadline) = self.navigation.directory_item_count_ready_at else {
            return false;
        };
        if Instant::now() < deadline
            || self.browser_wheel_burst_active()
            || self.preview.state.deferred_refresh_at.is_some()
        {
            return false;
        }

        self.navigation.directory_item_count_ready_at = None;
        self.submit_visible_directory_item_counts();
        false
    }

    pub(crate) fn pending_directory_item_count_timer(&self) -> Option<std::time::Duration> {
        self.navigation
            .directory_item_count_ready_at
            .map(|deadline| deadline.saturating_duration_since(Instant::now()))
    }

    fn submit_visible_directory_item_counts(&mut self) {
        let requests = self
            .visible_entry_indices()
            .into_iter()
            .filter_map(|index| {
                self.navigation.entries.get(index).and_then(|entry| {
                    entry.is_dir().then_some((
                        index.abs_diff(self.navigation.selected),
                        index,
                        entry,
                    ))
                })
            })
            .collect::<Vec<_>>();
        let mut requests = requests
            .into_iter()
            .filter_map(|(distance, index, entry)| {
                self.directory_item_count_request_for(entry)
                    .map(|request| (distance, index, request))
            })
            .collect::<Vec<_>>();
        requests.sort_by_key(|(distance, index, _)| (*distance, *index));

        for (_, _, request) in requests {
            let _ = self.jobs.scheduler.submit_directory_item_count(request);
        }
    }

    pub(super) fn should_redraw_for_directory_item_count(
        &self,
        path: &Path,
        modified: Option<SystemTime>,
        show_hidden: bool,
    ) -> bool {
        if self.effective_show_hidden() != show_hidden {
            return false;
        }

        self.visible_entry_indices().into_iter().any(|index| {
            self.navigation.entries.get(index).is_some_and(|entry| {
                entry.is_dir() && entry.path == path && entry.modified == modified
            })
        })
    }

    fn directory_item_count(&self, entry: &Entry) -> Option<usize> {
        let key = self.directory_item_count_key_for(entry)?;
        self.navigation
            .directory_item_count_cache
            .get(&key)
            .copied()
            .flatten()
    }

    fn directory_item_count_request_for(
        &self,
        entry: &Entry,
    ) -> Option<jobs::DirectoryItemCountRequest> {
        let key = self.directory_item_count_key_for(entry)?;
        if self
            .navigation
            .directory_item_count_cache
            .contains_key(&key)
        {
            return None;
        }
        Some(jobs::DirectoryItemCountRequest {
            path: key.path,
            modified: key.modified,
            show_hidden: key.show_hidden,
        })
    }

    fn directory_item_count_key_for(&self, entry: &Entry) -> Option<DirectoryItemCountKey> {
        entry.is_dir().then(|| DirectoryItemCountKey {
            path: entry.path.clone(),
            modified: entry.modified,
            show_hidden: self.effective_show_hidden(),
        })
    }

    pub(super) fn visible_entry_indices(&self) -> Vec<usize> {
        if self.navigation.entries.is_empty() {
            return Vec::new();
        }

        let cols = self.input.frame_state.metrics.cols.max(1);
        let rows_visible = self.input.frame_state.metrics.rows_visible.max(1);
        let start = self.navigation.scroll_row.saturating_mul(cols);
        let limit = rows_visible.saturating_mul(cols);
        (start..self.navigation.entries.len()).take(limit).collect()
    }
}