entrust-dialog 0.6.0

Interactive terminal dialogs for entrust
Documentation
use crate::input::{InputDialog, Update};
use std::borrow::Cow;
use std::cell::Cell;

#[derive(Debug, Default)]
pub(super) struct Completions {
    pub list: Vec<Cow<'static, str>>,
    index: Cell<usize>,
}

impl Completions {
    fn get_full_completion(&self, current_content: &str) -> Option<&str> {
        if self.list.is_empty() {
            return None;
        }
        let options: Vec<_> = self
            .list
            .iter()
            .filter(|&c| c.len() > current_content.len())
            .filter(|&c| c.starts_with(current_content))
            .map(|c| c.as_ref())
            .collect();
        if options.len() > self.index.get() {
            Some(options[self.index.get()])
        } else {
            self.index.set(0);
            options.into_iter().next()
        }
    }

    fn get_end_completion(&self, current_content: &str) -> Option<&str> {
        self.get_full_completion(current_content)
            .map(|c| c.trim_start_matches(current_content))
    }
}

impl<'p, 'c> InputDialog<'p, 'c> {
    pub(super) fn update_completions(&mut self, update: Update) {
        if update == Update::CycleCompletions {
            self.completions.index.set(self.completions.index.get() + 1)
        } else if update == Update::CycleCompletionsBackward {
            self.completions
                .index
                .set(self.completions.index.get().saturating_sub(1))
        }
    }
    pub(super) fn get_full_completion(&self) -> Option<&str> {
        self.completions
            .get_full_completion(self.current_content().as_str())
    }

    pub(super) fn get_end_completion(&self) -> Option<&str> {
        self.completions
            .get_end_completion(self.current_content().as_str())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::dialog::Dialog;
    use crate::input::Update;

    #[test]
    fn test_get_completion() {
        let dialog = InputDialog::default()
            .with_content("I held it truth")
            .with_completions(vec![
                "I held it truth, with him who sings".into(),
                "I held it truth, with him who sings / To one clear harp in divers tones".into(),
            ]);
        assert_eq!(
            Some("I held it truth, with him who sings"),
            dialog.get_full_completion(),
        );
        assert_eq!(Some(", with him who sings"), dialog.get_end_completion());

        let dialog = InputDialog::default()
            .with_content("That men may rise on stepping stones")
            .with_completions(vec!["Of their dead selves to higher things".into()]);
        assert_eq!(None, dialog.get_full_completion());

        let dialog = InputDialog::default()
            .with_content("But who shall forecast the years / And find in loss a gain to match")
            .with_completions(vec![
                "But who shall forecast the years / And find in loss a gain to match".into(),
            ]);
        assert_eq!(None, dialog.get_full_completion());
    }

    #[test]
    fn test_accept_completion() {
        let mut dialog = InputDialog::default()
            .with_content("Or reach a hand through")
            .with_completions(vec![
                "Or reach a hand thro' time to catch / The far-off interest of tears".into(),
            ]);
        dialog.perform_update(Update::MoveCursorRight).unwrap();
        assert_eq!("Or reach a hand through", dialog.current_content().as_str());

        dialog.perform_update(Update::DeleteBeforeCursor).unwrap();
        dialog.perform_update(Update::DeleteBeforeCursor).unwrap();
        dialog.perform_update(Update::DeleteBeforeCursor).unwrap();
        dialog.perform_update(Update::MoveCursorRight).unwrap();
        assert_eq!(
            "Or reach a hand thro' time to catch / The far-off interest of tears",
            dialog.current_content().as_str()
        );

        dialog.perform_update(Update::DeleteBeforeCursor).unwrap();
        assert_eq!(
            "Or reach a hand thro' time to catch / The far-off interest of tear",
            dialog.current_content().as_str()
        );
    }

    #[test]
    fn test_cycle_completion() {
        let mut dialog = InputDialog::default()
            .with_content("I h")
            .with_completions(vec![
                "I held it truth, with him who sings".into(),
                "I hold it true, whate'er befall".into(),
            ]);
        assert_eq!(
            Some("I held it truth, with him who sings"),
            dialog.get_full_completion()
        );
        dialog.perform_update(Update::CycleCompletions).unwrap();
        assert_eq!(
            Some("I hold it true, whate'er befall"),
            dialog.get_full_completion()
        );
        dialog.perform_update(Update::CycleCompletions).unwrap();
        assert_eq!(
            Some("I held it truth, with him who sings"),
            dialog.get_full_completion()
        );

        dialog.perform_update(Update::InsertChar('a')).unwrap();
        assert_eq!(None, dialog.get_full_completion());
        dialog.perform_update(Update::CycleCompletions).unwrap();
        assert_eq!(None, dialog.get_full_completion());
    }
}