use crate::cli::DocumentFormat;
use anyhow::{Context, Result};
use colored::*;
use inquire::{Autocomplete, CustomUserError, Select, Text};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Default)]
pub struct FilePathCompleter {
pub allowed_extensions: Vec<String>,
}
impl FilePathCompleter {
pub fn new(allowed_extensions: &[&str]) -> Self {
Self {
allowed_extensions: allowed_extensions.iter().map(|s| s.to_string()).collect(),
}
}
}
impl Autocomplete for FilePathCompleter {
fn get_suggestions(&mut self, input: &str) -> Result<Vec<String>, CustomUserError> {
let path = Path::new(input);
let (dir_to_scan, prefix) = if input.is_empty() {
(Path::new("."), "")
} else if input.ends_with('/') || input.ends_with('\\') {
(path, "")
} else {
let parent = path.parent().unwrap_or_else(|| Path::new("."));
let parent = if parent.as_os_str().is_empty() {
Path::new(".")
} else {
parent
};
let file_prefix = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("");
(parent, file_prefix)
};
let mut suggestions = Vec::new();
let read_dir = match fs::read_dir(dir_to_scan) {
Ok(entries) => entries,
Err(_) => return Ok(suggestions),
};
for entry in read_dir.flatten() {
let name_os = entry.file_name();
let name = match name_os.to_str() {
Some(n) => n,
None => continue,
};
if name.starts_with('.') && !prefix.starts_with('.') {
continue;
}
if name.to_lowercase().starts_with(&prefix.to_lowercase()) {
let is_dir = entry.file_type().map(|t| t.is_dir()).unwrap_or(false);
let full_path = entry.path();
let full_str = full_path.to_string_lossy().to_string();
if is_dir {
suggestions.push(format!("{}/", full_str));
} else if self.allowed_extensions.is_empty() {
suggestions.push(full_str);
} else {
let ext = full_path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase())
.unwrap_or_default();
if self.allowed_extensions.iter().any(|allowed| allowed.eq_ignore_ascii_case(&ext)) {
suggestions.push(full_str);
}
}
}
}
suggestions.sort();
Ok(suggestions)
}
fn get_completion(
&mut self,
_input: &str,
highlighted_suggestion: Option<String>,
) -> Result<inquire::autocompletion::Replacement, CustomUserError> {
Ok(highlighted_suggestion)
}
}
pub struct WizardResult {
pub format: DocumentFormat,
pub input_path: PathBuf,
}
pub fn run_wizard() -> Result<WizardResult> {
println!("{}", "==================================================".bright_cyan());
println!("{}", " papr ยท Document to PDF ".bold().bright_white());
println!("{}", "==================================================".bright_cyan());
println!();
let options = vec![
"1. Markdown (.md)",
"2. LaTeX (.tex)",
"3. Plain Text (.txt)",
];
let choice = Select::new("Select document format to compile:", options)
.prompt()
.context("Failed to get format selection")?;
let (format, default_ext, filter_exts) = match choice {
s if s.starts_with("1") => (DocumentFormat::Markdown, "md", vec!["md", "markdown"]),
s if s.starts_with("2") => (DocumentFormat::Latex, "tex", vec!["tex", "latex"]),
_ => (DocumentFormat::PlainText, "txt", vec!["txt", "text", "log"]),
};
println!();
println!("{}", format!("Selected: {:?} (.{})", format, default_ext).dimmed());
let completer = FilePathCompleter::new(&filter_exts);
let prompt_msg = format!("Enter path to input file (press [Tab] to autocomplete):");
let input_str = Text::new(&prompt_msg)
.with_autocomplete(completer)
.with_help_message("e.g. document.md, chapters/main.tex, notes.txt")
.prompt()
.context("Failed to get file path")?;
let path = PathBuf::from(input_str.trim());
if !path.exists() {
anyhow::bail!("File not found: {}", path.display());
}
Ok(WizardResult {
format,
input_path: path,
})
}