heh 0.6.3

A cross-platform terminal UI used for modifying file data in hex or ASCII.
Documentation
use std::fmt::{Debug, Display, Formatter};

use ratatui::style::Color;

pub(crate) const CHARACTER_NULL: char = '0';
pub(crate) const CHARACTER_WHITESPACE: char = '_';
pub(crate) const CHARACTER_CONTROL: char = '';
pub(crate) const CHARACTER_FILL: char = '';
pub(crate) const CHARACTER_UNKNOWN: char = '';

const COLOR_NULL: Color = Color::DarkGray;
const COLOR_ASCII: Color = Color::Cyan;
const COLOR_UNICODE: Color = Color::LightCyan;
const COLOR_WHITESPACE: Color = Color::Green;
const COLOR_CONTROL: Color = Color::Magenta;
const COLOR_FILL: Color = Color::LightCyan;
const COLOR_UNKNOWN: Color = Color::Yellow;

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Type {
    Ascii,
    Unicode(usize),
    Unknown,
}

impl Type {
    pub(crate) fn size(&self) -> usize {
        match self {
            Type::Ascii | Type::Unknown => 1,
            Type::Unicode(size) => *size,
        }
    }
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum Category {
    Null,
    Ascii,
    Unicode,
    Whitespace,
    Control,
    Fill,
    Unknown,
}

impl From<&char> for Category {
    fn from(character: &char) -> Self {
        if character == &'\0' {
            Category::Null
        } else if character.is_whitespace() {
            Category::Whitespace
        } else if character.is_control() {
            Category::Control
        } else if character.is_ascii() {
            Category::Ascii
        } else {
            Category::Unicode
        }
    }
}

impl Category {
    pub(crate) fn escape(&self, character: char) -> char {
        match self {
            Category::Null => CHARACTER_NULL,
            Category::Ascii | Category::Unicode => character,
            Category::Whitespace if character == ' ' => ' ',
            Category::Whitespace => CHARACTER_WHITESPACE,
            Category::Control => CHARACTER_CONTROL,
            Category::Fill => CHARACTER_FILL,
            Category::Unknown => CHARACTER_UNKNOWN,
        }
    }

    pub(crate) fn color(&self) -> &'static Color {
        match self {
            Category::Null => &COLOR_NULL,
            Category::Ascii => &COLOR_ASCII,
            Category::Unicode => &COLOR_UNICODE,
            Category::Whitespace => &COLOR_WHITESPACE,
            Category::Control => &COLOR_CONTROL,
            Category::Fill => &COLOR_FILL,
            Category::Unknown => &COLOR_UNKNOWN,
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct RichChar {
    character: char,
    category: Category,
}

impl RichChar {
    pub(crate) fn new(character: char, category: Category) -> Self {
        Self { character, category }
    }

    pub(crate) fn escape(&self) -> char {
        self.category.escape(self.character)
    }

    pub(crate) fn color(&self) -> &'static Color {
        self.category.color()
    }
}

impl Display for RichChar {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.character, f)
    }
}

impl From<RichChar> for char {
    fn from(rich_char: RichChar) -> Self {
        rich_char.character
    }
}

impl From<&RichChar> for char {
    fn from(rich_char: &RichChar) -> Self {
        rich_char.character
    }
}

impl From<RichChar> for String {
    fn from(rich_char: RichChar) -> Self {
        rich_char.character.to_string()
    }
}

impl From<&RichChar> for String {
    fn from(rich_char: &RichChar) -> Self {
        rich_char.character.to_string()
    }
}