use std::io::ErrorKind; use inquire::{ autocompletion::{Autocomplete, Replacement}, CustomUserError, Text, }; fn main() { eprintln!("This is in no way representative of best practices when dealing with file systems and OS calls"); eprintln!(); eprintln!("It is a simple example to showcase autocompletion features and has not been battle-tested nor tested against bugs."); eprintln!(); let current_dir = std::env::current_dir().unwrap(); let help_message = format!("Current directory: {}", current_dir.to_string_lossy()); let ans = Text::new("Profile picture:") .with_autocomplete(FilePathCompleter::default()) .with_help_message(&help_message) .prompt(); match ans { Ok(path) => println!("Path: {path}"), Err(error) => println!("Error with questionnaire, try again later: {error:?}"), } } #[derive(Clone, Default)] pub struct FilePathCompleter { input: String, paths: Vec, lcp: String, } impl FilePathCompleter { fn update_input(&mut self, input: &str) -> Result<(), CustomUserError> { if input == self.input { return Ok(()); } self.input = input.to_owned(); self.paths.clear(); let input_path = std::path::PathBuf::from(input); let fallback_parent = input_path .parent() .map(|p| { if p.to_string_lossy() == "" { std::path::PathBuf::from(".") } else { p.to_owned() } }) .unwrap_or_else(|| std::path::PathBuf::from(".")); let scan_dir = if input.ends_with('/') { input_path } else { fallback_parent.clone() }; let entries = match std::fs::read_dir(scan_dir) { Ok(read_dir) => Ok(read_dir), Err(err) if err.kind() == ErrorKind::NotFound => std::fs::read_dir(fallback_parent), Err(err) => Err(err), }? .collect::, _>>()?; let mut idx = 0; let limit = 15; while idx < entries.len() && self.paths.len() < limit { let entry = entries.get(idx).unwrap(); let path = entry.path(); let path_str = if path.is_dir() { format!("{}/", path.to_string_lossy()) } else { path.to_string_lossy().to_string() }; if path_str.starts_with(&self.input) && path_str.len() != self.input.len() { self.paths.push(path_str); } idx = idx.saturating_add(1); } self.lcp = self.longest_common_prefix(); Ok(()) } fn longest_common_prefix(&self) -> String { let mut ret: String = String::new(); let mut sorted = self.paths.clone(); sorted.sort(); if sorted.is_empty() { return ret; } let mut first_word = sorted.first().unwrap().chars(); let mut last_word = sorted.last().unwrap().chars(); loop { match (first_word.next(), last_word.next()) { (Some(c1), Some(c2)) if c1 == c2 => { ret.push(c1); } _ => return ret, } } } } impl Autocomplete for FilePathCompleter { fn get_suggestions(&mut self, input: &str) -> Result, CustomUserError> { self.update_input(input)?; Ok(self.paths.clone()) } fn get_completion( &mut self, input: &str, highlighted_suggestion: Option, ) -> Result { self.update_input(input)?; Ok(match highlighted_suggestion { Some(suggestion) => Replacement::Some(suggestion), None => match self.lcp.is_empty() { true => Replacement::None, false => Replacement::Some(self.lcp.clone()), }, }) } }