use crate::error::Result;
use crate::terminal::Terminal;
use crossterm::style::{Attribute, Color, SetForegroundColor};
#[derive(Debug)]
pub(crate) struct Autocomplete {
keywords: Vec<String>,
common: String,
pub(crate) tabbed: bool,
pub(crate) tab_idx: usize,
}
impl Autocomplete {
pub fn new() -> Self {
Autocomplete {
keywords: Vec::new(),
common: String::new(),
tabbed: false,
tab_idx: 0,
}
}
pub(crate) fn get_common(&self) -> &String {
&self.common
}
pub(crate) fn get_keywords(&self) -> &Vec<String> {
&self.keywords
}
pub(crate) fn amortize(&mut self) {
if let Some(max_len) = self.keywords.iter().map(|x| x.len()).max() {
for word in self.keywords.iter_mut() {
for _ in 0..max_len - word.len() {
word.push(' ');
}
}
}
}
pub(crate) fn reset_tabbed(&mut self) {
self.tabbed = false;
if self.keywords.is_empty() {
self.tab_idx = 0;
} else {
self.tab_idx = self.keywords.len() - 1;
}
}
pub(crate) fn incr_idx(&mut self) {
if !self.keywords.is_empty() {
self.tab_idx = (self.tab_idx + 1) % self.keywords.len();
}
}
}
impl<'a> Autocomplete {
pub(crate) fn update(&mut self, word: &'a str, keywords: &'a [String]) {
if word.is_empty() {
self.keywords = Vec::with_capacity(0);
self.common = String::new();
return;
}
let mut similar: Vec<String> = keywords
.iter()
.filter(|x| x.starts_with(word))
.cloned()
.collect();
self.keywords = similar.clone();
self.common = if let Some(common) = return_common_str_from_sorted_collection(&mut similar) {
common
} else {
String::new()
};
}
pub(crate) fn get_current_tabbed_autocomplete(&self) -> Option<String> {
if self.tabbed {
self.keywords
.get(self.tab_idx)
.map(|keyword| keyword.clone().trim_end().to_string())
} else {
None
}
}
pub(crate) fn print_same_line_autocompleted(
&self,
color: Color,
buffer: &str,
terminal: &Terminal,
) -> Result<()> {
if !self.common.is_empty() {
terminal.save_cursor()?;
print!(
"{}{}{}",
SetForegroundColor(color),
self.common.split_at(buffer.len()).1,
Attribute::Reset
);
terminal.restore_cursor()?;
} else {
terminal.clear_from_cursor()?;
}
Ok(())
}
}
fn return_common_str_from_sorted_collection(collection: &mut [String]) -> Option<String> {
let copied_collection = collection.to_owned();
while let Some(first) = collection.first_mut() {
if copied_collection
.iter()
.all(|x| x.starts_with(first.as_str()))
{
return Some(first.clone());
} else {
first.pop();
}
}
None
}
mod tests {
#[cfg(test)]
use super::*;
#[test]
fn autocomplete_empty_input() {
let word = "".to_owned();
let keywords = vec![
"non".to_owned(),
"important".to_owned(),
"for".to_owned(),
"this".to_owned(),
"test".to_owned(),
];
let mut autocompleted = Autocomplete::new();
autocompleted.update(&word, &keywords);
assert_eq!(autocompleted.get_keywords(), &Vec::<String>::new());
assert_eq!(autocompleted.get_common(), &String::new());
let word = "random_word".to_owned();
let keywords: Vec<String> = Vec::new();
autocompleted.update(&word, &keywords);
assert_eq!(autocompleted.get_keywords(), &Vec::<String>::new());
assert_eq!(autocompleted.get_common(), &String::new());
let word = "".to_owned();
let keywords: Vec<String> = Vec::new();
autocompleted.update(&word, &keywords);
assert_eq!(autocompleted.get_keywords(), &Vec::<String>::new());
assert_eq!(autocompleted.get_common(), &String::new());
}
#[test]
fn autocomplete_returns_as_expected() {
let word = "some_word".to_owned();
let keywords = vec![
"non".to_owned(),
"important".to_owned(),
"for".to_owned(),
"this".to_owned(),
"test".to_owned(),
];
let mut autocompleted = Autocomplete::new();
autocompleted.update(&word, &keywords);
assert_eq!(autocompleted.get_keywords(), &Vec::<String>::new());
assert_eq!(autocompleted.get_common(), &String::new());
let word = "some_word".to_owned();
let keywords = vec![
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
];
autocompleted.update(&word, &keywords);
assert_eq!(
autocompleted.get_keywords(),
&vec![
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned()
]
);
assert_eq!(autocompleted.get_common(), &"some_word".to_owned());
let word = "s".to_owned();
let keywords = vec![
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
];
autocompleted.update(&word, &keywords);
assert_eq!(
autocompleted.get_keywords(),
&vec![
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned(),
"some_word".to_owned()
]
);
assert_eq!(autocompleted.get_common(), &"some_word".to_owned());
let word = "s".to_owned();
let keywords = vec![
"some_word".to_owned(),
"some_other_word".to_owned(),
"none".to_owned(),
];
autocompleted.update(&word, &keywords);
assert_eq!(
autocompleted.get_keywords(),
&vec!["some_word".to_owned(), "some_other_word".to_owned(),]
);
assert_eq!(autocompleted.get_common(), &"some_".to_owned());
let word = "some_w".to_owned();
let keywords = vec![
"some_word".to_owned(),
"some_other_word".to_owned(),
"none".to_owned(),
];
autocompleted.update(&word, &keywords);
assert_eq!(autocompleted.get_keywords(), &vec!["some_word".to_owned(),]);
assert_eq!(autocompleted.get_common(), &"some_word".to_owned());
}
#[test]
fn amortized() {
let vec = vec!["_a".to_owned(), "_ab".to_owned(), "_abc".to_owned()];
let word = "_".to_owned();
let mut autocomplete = Autocomplete::new();
autocomplete.update(&word, &vec);
autocomplete.amortize();
assert_eq!(autocomplete.get_keywords(), &vec!["_a ", "_ab ", "_abc"]);
let vec = vec!["_aa".to_owned(), "_bb".to_owned(), "_cc".to_owned()];
autocomplete.update(&word, &vec);
autocomplete.amortize();
assert_eq!(autocomplete.get_keywords(), &vec!["_aa", "_bb", "_cc"]);
let vec = Vec::with_capacity(0);
autocomplete.update(&word, &vec);
autocomplete.amortize();
let return_vec: Vec<String> = Vec::with_capacity(0);
assert_eq!(autocomplete.get_keywords(), &return_vec);
}
#[test]
fn increment_index_wraps_around() {
let mut autocomplete = Autocomplete::new();
let vec = vec!["_a".to_owned(), "_ab".to_owned(), "_abc".to_owned()];
let word = "_".to_owned();
autocomplete.update(&word, &vec);
autocomplete.incr_idx();
assert_eq!(autocomplete.tab_idx, 1);
autocomplete.incr_idx();
assert_eq!(autocomplete.tab_idx, 2);
autocomplete.incr_idx();
assert_eq!(autocomplete.tab_idx, 0);
autocomplete.incr_idx();
assert_eq!(autocomplete.tab_idx, 1);
}
}