use crate::db::{Dir, Epoch}; use crate::error::SilentExit; use anyhow::{bail, Context, Result}; use std::io::Write; use std::process::{Child, Command, Stdio}; use std::str; pub struct Fzf { child: Child, lines: Vec, } impl Fzf { pub fn new() -> Result { let child = Command::new("fzf") .args(&["-n2..", "--no-sort"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .context("could not launch fzf")?; Ok(Fzf { child, lines: Vec::new(), }) } pub fn write_dir(&mut self, dir: &Dir, now: Epoch) { let frecency = dir.get_frecency(now); let frecency_scaled = if frecency > 9999.0 { 9999 } else if frecency > 0.0 { frecency as u32 } else { 0 }; self.lines .push(format!("{:>4} {}", frecency_scaled, dir.path)); } pub fn wait_selection(mut self) -> Result> { // unwrap() is safe here since we have captured `stdin` let stdin = self.child.stdin.as_mut().unwrap(); self.lines.sort_unstable_by(|line1, line2| line2.cmp(line1)); for line in self.lines.iter() { writeln!(stdin, "{}", line).context("could not write into fzf stdin")?; } let output = self .child .wait_with_output() .context("wait failed on fzf")?; match output.status.code() { // normal exit Some(0) => { let path_bytes = output .stdout .get(12..output.stdout.len().saturating_sub(1)) .context("fzf returned invalid output")?; let path_str = str::from_utf8(path_bytes).context("invalid utf-8 sequence in fzf output")?; Ok(Some(path_str.to_string())) } // no match Some(1) => Ok(None), // error Some(2) => bail!("fzf returned an error"), // terminated by a signal Some(code @ 130) => bail!(SilentExit { code }), Some(128..=254) | None => bail!("fzf was terminated"), // unknown _ => bail!("fzf returned an unknown error"), } } }