demand 2.0.0

A CLI prompt library
Documentation
use std::{fs, path::Path};

use demand::{Autocomplete, Input};

#[derive(Clone)]
struct FileCompleter;

impl Autocomplete for FileCompleter {
    fn get_suggestions(&mut self, input: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
        let (dir, prefix) = if input.is_empty() {
            (".", "")
        } else if input.ends_with('/') || input.ends_with('\\') {
            (input, "")
        } else {
            let path = Path::new(input);
            match (path.parent(), path.file_name()) {
                (Some(parent), Some(name)) => {
                    let parent_str = if parent.as_os_str().is_empty() {
                        "."
                    } else {
                        parent.to_str().unwrap_or(".")
                    };
                    (parent_str, name.to_str().unwrap_or(""))
                }
                _ => (".", input),
            }
        };

        let mut suggestions = Vec::new();

        if let Ok(entries) = fs::read_dir(dir) {
            for entry in entries.filter_map(|e| e.ok()) {
                let name = entry.file_name();
                let name_str = name.to_string_lossy();

                if prefix.is_empty() || name_str.to_lowercase().starts_with(&prefix.to_lowercase())
                {
                    let full_path = if dir == "." {
                        name_str.to_string()
                    } else {
                        format!("{}/{}", dir.trim_end_matches('/'), name_str)
                    };

                    let display = if entry.path().is_dir() {
                        format!("{}/", full_path)
                    } else {
                        full_path
                    };

                    suggestions.push(display);
                }
            }
        }

        suggestions.sort();
        Ok(suggestions)
    }

    fn get_completion(
        &mut self,
        _input: &str,
        highlighted_suggestion: Option<&str>,
    ) -> Result<Option<String>, Box<dyn std::error::Error>> {
        Ok(highlighted_suggestion.map(|s| s.to_string()))
    }
}

fn main() {
    let input = Input::new("Select a file:")
        .description("Type a path or browse with arrow keys")
        .placeholder("./")
        .autocomplete(FileCompleter)
        .max_suggestions_display(10);

    match input.run() {
        Ok(path) => println!("Selected: {}", path),
        Err(e) => {
            if e.kind() == std::io::ErrorKind::Interrupted {
                println!("Cancelled");
            } else {
                panic!("Error: {}", e);
            }
        }
    }
}