bearask 0.5.0

A really fast and featureful CLI prompting lib
Documentation
use {crossterm::terminal, miette::IntoDiagnostic, std::io::Write};

#[macro_export]
macro_rules! validation {
    (valid) => {
        Ok($crate::Validation::Valid)
    };

    (invalid) => {
        Ok($crate::Validation::Invalid::Default)
    };

    (invalid $msg:expr) => {
        Ok($crate::Validation::Invalid($crate::ErrorMessage::Custom(
            $msg.into(),
        )))
    };
}

pub(crate) fn visible_width(s: &str) -> usize {
    let mut width = 0;
    let mut chars = s.chars();
    while let Some(c) = chars.next() {
        if c == '\x1b' {
            match chars.next() {
                Some('[') => {
                    for c in chars.by_ref() {
                        if ('@'..='~').contains(&c) {
                            break;
                        }
                    }
                }
                Some(']') => {
                    let mut prev = '\0';
                    for c in chars.by_ref() {
                        if c == '\x07' || (prev == '\x1b' && c == '\\') {
                            break;
                        }
                        prev = c;
                    }
                }
                _ => {}
            }
        } else if !c.is_control() {
            width += 1;
        }
    }
    width
}

pub(crate) fn physical_rows(content_width: usize, terminal_width: u16) -> usize {
    let tw = terminal_width as usize;
    if tw == 0 || content_width == 0 {
        return 1;
    }
    content_width.div_ceil(tw)
}

pub(crate) fn term_width() -> u16 {
    terminal::size().map(|(w, _)| w).unwrap_or(80)
}

pub(crate) fn writeln_physical(
    out: &mut impl Write,
    line: &str,
    tw: u16,
) -> miette::Result<usize> {
    writeln!(out, "{}", line).into_diagnostic()?;
    Ok(physical_rows(visible_width(line), tw))
}

#[derive(Debug, Clone)]
pub struct CursorGuard;

impl CursorGuard {
    pub fn new() -> miette::Result<Self> {
        crossterm::execute!(std::io::stdout(), crossterm::cursor::Hide).into_diagnostic()?;
        crossterm::execute!(
            std::io::stdout(),
            crossterm::cursor::SetCursorStyle::BlinkingBar
        )
        .into_diagnostic()?;
        Ok(Self)
    }
}

impl Drop for CursorGuard {
    fn drop(&mut self) {
        let _ = crossterm::execute!(std::io::stdout(), crossterm::cursor::Show);
        let _ = crossterm::execute!(
            std::io::stdout(),
            crossterm::cursor::SetCursorStyle::DefaultUserShape
        );
    }
}

impl Default for CursorGuard {
    fn default() -> Self {
        Self::new().expect("Failed to initialize cursor guard")
    }
}