extern crate picleo;
use anyhow::Result;
use clap::Parser;
use picleo::{picker::Picker, selectable::SelectableItem};
use std::{
fmt, fs,
io::{self, BufRead},
path::PathBuf,
};
#[derive(Debug, Clone)]
struct DisplayPath(PathBuf);
impl fmt::Display for DisplayPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.display())
}
}
impl From<PathBuf> for DisplayPath {
fn from(path: PathBuf) -> Self {
DisplayPath(path)
}
}
impl AsRef<PathBuf> for DisplayPath {
fn as_ref(&self) -> &PathBuf {
&self.0
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(name = "DIRS")]
dirs: Vec<PathBuf>,
#[arg(short, long)]
recursive: bool,
#[arg(short, long)]
threaded: bool,
#[arg(short, long)]
preview: Option<String>,
}
fn main() -> Result<()> {
let args = Args::parse();
if !args.dirs.is_empty() {
load_from_args(args)?
} else {
load_from_stdin(args)?
}
Ok(())
}
fn load_from_args(args: Args) -> Result<(), anyhow::Error> {
let has_files = args.dirs.iter().any(|path| path.is_file());
let has_dirs = args.dirs.iter().any(|path| path.is_dir());
let dirs = args.dirs;
let preview_command = args.preview;
if has_files && !has_dirs {
let mut picker = Picker::<String>::new();
if let Some(preview_cmd) = preview_command.clone() {
picker.set_preview_command(preview_cmd);
}
for file_path in dirs {
if file_path.is_file() {
if args.threaded {
picker.inject_items_threaded(move |i| {
read_file_lines(&file_path, i);
});
} else {
picker.inject_items(|i| {
read_file_lines(&file_path, i);
});
}
}
}
match picker.run() {
Ok(selected_items) => {
for line in selected_items.existing_values() {
println!("{}", line)
}
for requested_line in selected_items.requested_values() {
println!("{}", requested_line)
}
}
Err(err) => {
println!("{err:?}");
return Err(anyhow::anyhow!("{:?}", err));
}
}
} else {
let mut picker = Picker::<DisplayPath>::new();
if let Some(preview_cmd) = preview_command {
picker.set_preview_command(preview_cmd);
}
for path in dirs {
if path.is_file() {
if args.threaded {
picker.inject_items_threaded(move |i| {
i.push(SelectableItem::new(DisplayPath(path)), |item, columns| {
columns[0] = item.to_string().into()
});
});
} else {
picker.inject_items(|i| {
i.push(
SelectableItem::new(DisplayPath(path.clone())),
|item, columns| columns[0] = item.to_string().into(),
);
});
}
} else if path.is_dir() {
if args.threaded {
picker.inject_items_threaded(move |i| {
if args.recursive {
walk_dir_recursive(path, i);
} else {
walk_dir(path, i);
}
});
} else {
picker.inject_items(|i| {
if args.recursive {
walk_dir_recursive(path, i);
} else {
walk_dir(path, i);
}
});
}
}
}
match picker.run() {
Ok(selected_items) => {
for path in selected_items.existing_values() {
println!("{}", path.0.display())
}
for requested_path in selected_items.requested_values() {
println!("{}", requested_path)
}
}
Err(err) => {
println!("{err:?}");
return Err(anyhow::anyhow!("{:?}", err));
}
}
}
Ok(())
}
fn load_from_stdin(args: Args) -> Result<(), anyhow::Error> {
let mut picker = Picker::<String>::new();
if let Some(preview_cmd) = args.preview {
picker.set_preview_command(preview_cmd);
}
if args.threaded {
picker.inject_items_threaded(|i| {
for line in io::stdin().lock().lines().map_while(Result::ok) {
i.push(SelectableItem::new(line), |item, columns| {
columns[0] = item.to_string().into()
});
}
});
} else {
picker.inject_items(|i| {
for line in io::stdin().lock().lines().map_while(Result::ok) {
i.push(SelectableItem::new(line), |item, columns| {
columns[0] = item.to_string().into()
});
}
});
}
match picker.run() {
Ok(selected_items) => {
for line in selected_items.existing_values() {
println!("{}", line)
}
for requested_line in selected_items.requested_values() {
println!("{}", requested_line)
}
}
Err(err) => {
println!("{err:?}");
return Err(anyhow::anyhow!("{:?}", err));
}
}
Ok(())
}
fn walk_dir(dir: PathBuf, i: &nucleo::Injector<SelectableItem<DisplayPath>>) {
if let Ok(entries) = fs::read_dir(&dir) {
for entry in entries.flatten() {
let path = entry.path();
i.push(SelectableItem::new(DisplayPath(path)), |item, columns| {
columns[0] = item.to_string().into()
});
}
}
}
fn walk_dir_recursive(dir: PathBuf, injector: &nucleo::Injector<SelectableItem<DisplayPath>>) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk_dir_recursive(path, injector);
} else {
injector.push(SelectableItem::new(DisplayPath(path)), |item, columns| {
columns[0] = item.to_string().into()
});
}
}
}
}
fn read_file_lines(file_path: &PathBuf, injector: &nucleo::Injector<SelectableItem<String>>) {
if let Ok(contents) = fs::read_to_string(file_path) {
for line in contents.lines() {
injector.push(SelectableItem::new(line.to_string()), |item, columns| {
columns[0] = item.to_string().into()
});
}
}
}