xdbm/src/inquire_filepath_completer.rs
2024-06-19 17:37:19 +09:00

119 lines
3.2 KiB
Rust

use inquire::{
autocompletion::{Autocomplete, Replacement},
CustomUserError,
};
use std::io::ErrorKind;
#[derive(Clone, Default)]
pub struct FilePathCompleter {
input: String,
paths: Vec<String>,
lcp: String,
}
impl FilePathCompleter {
fn update_input(&mut self, input: &str) -> Result<(), CustomUserError> {
if input == self.input {
return Ok(());
}
input.clone_into(&mut self.input);
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::<Result<Vec<_>, _>>()?;
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<Vec<String>, CustomUserError> {
self.update_input(input)?;
Ok(self.paths.clone())
}
fn get_completion(
&mut self,
input: &str,
highlighted_suggestion: Option<String>,
) -> Result<Replacement, CustomUserError> {
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()),
},
})
}
}