yofi 0.2.1

minimalistic menu for wayland
Documentation
use std::cmp::Reverse;
use std::ffi::CString;

use anyhow::{Context, Result};

use super::{Entry, EvalInfo};
use crate::usage_cache::Usage;
use crate::DesktopEntry;

const CACHE_PATH: &str = concat!(crate::prog_name!(), ".cache");

pub struct AppsMode {
    entries: Vec<DesktopEntry>,
    term: Vec<CString>,
    usage: Usage,
}

impl AppsMode {
    pub fn new(mut entries: Vec<DesktopEntry>, term: Vec<CString>) -> Self {
        let usage = Usage::from_path(CACHE_PATH);

        entries.sort_by_key(|e| Reverse(usage.entry_count(&e.desktop_fname)));

        Self {
            entries,
            term,
            usage,
        }
    }

    pub fn eval(&mut self, info: EvalInfo<'_>) -> Result<std::convert::Infallible> {
        let idx = info.index.context("no app remain to launch")?;
        let entry = &self.entries[idx];
        let exec = if info.subindex == 0 {
            &entry.entry.exec
        } else {
            &entry.actions[info.subindex - 1].exec
        };

        let args = shlex::split(exec)
            .with_context(|| format!("invalid app command line: {exec}"))?
            .into_iter()
            .filter(|s| !s.starts_with('%')) // TODO: use placeholders somehow
            .map(|s| CString::new(s).expect("invalid argument"));

        self.usage
            .increment_entry_usage(entry.desktop_fname.clone());
        self.usage.try_update_cache(CACHE_PATH);

        let term = if entry.is_terminal {
            Some(std::mem::take(&mut self.term))
        } else {
            None
        };

        crate::exec::exec(term, args, info.input_value)
    }

    pub fn entries_len(&self) -> usize {
        self.entries.len()
    }

    pub fn subentries_len(&self, idx: usize) -> usize {
        self.entries.get(idx).map(|e| e.actions.len()).unwrap_or(0)
    }

    pub fn entry(&self, idx: usize, subidx: usize) -> Entry<'_> {
        let entry = &self.entries[idx];

        Entry {
            name: entry.entry.name.as_ref(),
            subname: Some(entry.subname(subidx).unwrap_or("Default Action")),
            icon: entry.icon(subidx).and_then(|i| i.as_image()),
        }
    }

    pub fn text_entries(&self) -> impl super::ExactSizeIterator<Item = &str> {
        self.entries.iter().map(|e| e.name.as_str())
    }
}