entrust-dialog 0.6.0

Interactive terminal dialogs for entrust
Documentation
use crate::dialog::DialogState;
use crate::input::InputDialog;
use crate::input::prompt::Prompt;
use crate::input::validator::Validator;
use std::mem;

#[derive(Debug, Clone)]
pub struct Confirmation<'c> {
    prompt: Prompt<'c>,
    validation_message: &'static str,
    is_active: bool,
}

impl<'c> Confirmation<'c> {
    pub fn new(prompt: Prompt<'c>) -> Self {
        Confirmation {
            prompt,
            validation_message: "no match",
            is_active: false,
        }
    }

    pub fn with_validation_message(mut self, validation_message: &'static str) -> Self {
        self.validation_message = validation_message;
        self
    }
}

impl<'p, 'c> InputDialog<'p, 'c> {
    pub(super) fn confirm(&mut self) {
        match self.confirmation.as_mut() {
            None => self.state = DialogState::Completed,
            Some(confirmation) if confirmation.is_active => self.state = DialogState::Completed,
            Some(confirmation) => {
                let first = mem::take(&mut self.content);
                self.cursor.set_index(0);
                let message = confirmation.validation_message;
                self.validator = Validator::new(move |chars| {
                    if chars == first {
                        None
                    } else {
                        Some(message.into())
                    }
                });
                confirmation.is_active = true;
            }
        }
    }

    pub(super) fn prompt_with_confirmation(&self) -> Prompt<'_> {
        self.confirmation
            .as_ref()
            .and_then(|c| {
                if c.is_active {
                    Some(c.prompt.clone())
                } else {
                    None
                }
            })
            .unwrap_or(self.prompt.clone())
    }
}

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

    #[test]
    fn test() {
        let prompt_header = "header prompt";
        let prompt_inline = "inline prompt";
        let confirmation_header = "repeat";
        let validation_message = "no match";
        let mut dialog = InputDialog::default()
            .with_prompt(Prompt::new(prompt_header, prompt_inline))
            .with_confirmation(
                Confirmation::new(Prompt::header(confirmation_header))
                    .with_validation_message(validation_message),
            );

        dialog.perform_update(Update::InsertChar('a')).unwrap();
        assert!(!dialog.confirmation.as_ref().unwrap().is_active);
        assert_eq!(
            Line::raw(prompt_header),
            dialog.prompt_with_confirmation().header
        );

        dialog.perform_update(Update::Confirm).unwrap();
        assert!(dialog.content.is_empty());
        assert!(dialog.confirmation.as_ref().unwrap().is_active);
        assert_eq!(
            Line::raw(confirmation_header),
            dialog.prompt_with_confirmation().header
        );

        dialog.perform_update(Update::InsertChar('b')).unwrap();
        dialog.perform_update(Update::Confirm).unwrap();
        assert_eq!(Some(validation_message.into()), dialog.validation_message());
        assert_eq!(DialogState::Pending, dialog.state);

        dialog.perform_update(Update::DeleteBeforeCursor).unwrap();
        dialog.perform_update(Update::InsertChar('a')).unwrap();
        assert_eq!(None, dialog.validation_message());
        dialog.perform_update(Update::Confirm).unwrap();
        assert_eq!(DialogState::Completed, dialog.state);
    }
}