#![cfg(not(target_os = "emscripten"))]
use anyhow::{Context, Result};
use std::io::Cursor;
use crate::fuzzy::{PickOptions, PickResult};
use regex::Regex;
use skim::Skim;
use skim::prelude::{RankCriteria, SkimItemReader, SkimItemReaderOption, SkimOptionsBuilder};
pub fn pick(lines: &[String], opts: &PickOptions<'_>) -> Result<PickResult> {
let tiebreak = parse_tiebreak(opts.tiebreak)?;
let with_nth = parse_field_spec(opts.with_nth);
let mut builder = SkimOptionsBuilder::default();
builder
.prompt(opts.prompt.to_string())
.multi(opts.multi)
.delimiter(Regex::new(r"\t").expect("static tab regex"))
.with_nth(with_nth)
.tiebreak(vec![tiebreak])
.reverse(true)
.height("100%".to_string());
if let Some(preview) = opts.preview {
builder.preview(crate::fuzzy::substitute_exe_placeholder(preview));
builder.preview_window(opts.preview_window);
}
if let Some(header) = opts.header {
builder.header(header.to_string());
}
let options = builder.build().context("build skim options")?;
let input = lines.join("\n");
let reader = SkimItemReader::new(SkimItemReaderOption::from_options(&options));
let items = reader.of_bufread(Cursor::new(input));
let output = Skim::run_with(options, Some(items))
.map_err(|e| anyhow::anyhow!("skim picker failed: {e}"))?;
if output.is_abort {
return Ok(PickResult::Cancelled);
}
if output.selected_items.is_empty() {
return Ok(PickResult::NoMatch);
}
let picked: Vec<String> = output
.selected_items
.iter()
.map(|item| item.output().to_string())
.collect();
Ok(PickResult::Selected(picked))
}
fn parse_tiebreak(s: &str) -> Result<RankCriteria> {
match s {
"index" => Ok(RankCriteria::Index),
"score" => Ok(RankCriteria::Score),
"begin" => Ok(RankCriteria::Begin),
"end" => Ok(RankCriteria::End),
"length" => Ok(RankCriteria::Length),
other => anyhow::bail!("unsupported tiebreak `{other}` for embedded picker"),
}
}
fn parse_field_spec(s: &str) -> Vec<String> {
s.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_tiebreak_known_values_map_through() {
assert!(matches!(
parse_tiebreak("index").unwrap(),
RankCriteria::Index
));
assert!(matches!(
parse_tiebreak("score").unwrap(),
RankCriteria::Score
));
}
#[test]
fn parse_tiebreak_rejects_unknown_value() {
assert!(parse_tiebreak("nope").is_err());
}
#[test]
fn parse_field_spec_splits_on_comma_and_trims() {
assert_eq!(parse_field_spec("2.."), vec!["2..".to_string()]);
assert_eq!(
parse_field_spec("1,3, 5"),
vec!["1".to_string(), "3".to_string(), "5".to_string()]
);
}
#[test]
fn parse_field_spec_drops_empty_entries() {
assert_eq!(parse_field_spec(",,2"), vec!["2".to_string()]);
assert_eq!(parse_field_spec(""), Vec::<String>::new());
}
}