#![cfg(not(target_os = "emscripten"))]
use anyhow::{Context, Result};
use std::io::{IsTerminal, Write};
use std::process::{Command, Stdio};
pub fn available() -> bool {
if !std::io::stdin().is_terminal() || !std::io::stderr().is_terminal() {
return false;
}
which("fzf").is_some()
}
fn which(cmd: &str) -> Option<std::path::PathBuf> {
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
let candidate = dir.join(cmd);
if candidate.is_file() {
return Some(candidate);
}
}
None
}
pub fn pick(lines: &[String], opts: &PickOptions<'_>) -> Result<Vec<String>> {
let mut args: Vec<String> = vec![
"--delimiter=\t".into(),
format!("--with-nth={}", opts.with_nth),
format!("--prompt={}", opts.prompt),
format!("--tiebreak={}", opts.tiebreak),
];
if opts.multi {
args.push("--multi".into());
}
if let Some(preview) = opts.preview {
args.push(format!("--preview={}", preview));
args.push("--preview-window=right:60%:wrap".into());
}
if let Some(header) = opts.header {
args.push(format!("--header={}", header));
}
let mut child = Command::new("fzf")
.args(&args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn fzf")?;
{
let stdin = child
.stdin
.as_mut()
.ok_or_else(|| anyhow::anyhow!("Failed to open fzf stdin"))?;
for line in lines {
stdin.write_all(line.as_bytes())?;
stdin.write_all(b"\n")?;
}
}
let output = child.wait_with_output().context("Failed to wait on fzf")?;
match output.status.code() {
Some(0) => {
let text = String::from_utf8_lossy(&output.stdout);
Ok(text.lines().map(|s| s.to_string()).collect())
}
Some(1) | Some(130) => Ok(Vec::new()),
_ => anyhow::bail!("fzf exited with status {:?}", output.status),
}
}
pub struct PickOptions<'a> {
pub with_nth: &'a str,
pub prompt: &'a str,
pub preview: Option<&'a str>,
pub header: Option<&'a str>,
pub tiebreak: &'a str,
pub multi: bool,
}
impl Default for PickOptions<'_> {
fn default() -> Self {
Self {
with_nth: "2..",
prompt: "> ",
preview: None,
header: None,
tiebreak: "index",
multi: false,
}
}
}
pub fn print_recipe(provider: &str, project_keyed: bool) {
if project_keyed {
eprintln!(
"Interactive selection needs `fzf` on PATH and a TTY.\n\
\n\
Manual recipe:\n \
path list {provider} --format tsv \\\n \
| fzf --delimiter=$'\\t' --with-nth=3.. \\\n \
--preview 'path show {provider} --project {{1}} --session {{2}}' \\\n \
| awk -F'\\t' '{{print $1 \"\\n\" $2}}' \\\n \
| xargs -L2 sh -c 'path derive {provider} --project \"$1\" --session \"$2\"' --\n"
);
} else {
eprintln!(
"Interactive selection needs `fzf` on PATH and a TTY.\n\
\n\
Manual recipe:\n \
path list {provider} --format tsv \\\n \
| fzf --delimiter=$'\\t' --with-nth=2.. \\\n \
--preview 'path show {provider} --session {{1}}' \\\n \
| cut -f1 \\\n \
| xargs -I{{}} path derive {provider} --session {{}}\n"
);
}
}