utu 0.0.1

TUI pixel art editor using plain text files
Documentation
// AppCommand?
#[derive(Debug, Clone)]
pub enum EditorCommand {
    Quit,
    Legend,
    Preview,
    Background(char),
    Cancel,
    Save,
    Undo,
    // reload
    // call or execute or shell
    Scope(String),
    PrevLine,
    NextLine,
    PrevChar,
    NextChar,
    Dot(char),
    MarkStroke,
    MarkLine,
    MarkRect,
    MarkFilledRect,
    MarkFill,
    Cut,
    Copy,
    Paste,
}

impl std::fmt::Display for EditorCommand {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EditorCommand::Quit => write!(f, "quit"),
            EditorCommand::Legend => write!(f, "legend"),
            EditorCommand::Preview => write!(f, "preview"),
            EditorCommand::Background(c) => write!(f, "bg({})", c),
            EditorCommand::Cancel => write!(f, "cancel"),
            EditorCommand::Save => write!(f, "save"),
            EditorCommand::Undo => write!(f, "undo"),
            EditorCommand::Scope(s) => write!(f, "scope({})", s),
            EditorCommand::PrevLine => write!(f, "prev-line"),
            EditorCommand::NextLine => write!(f, "next-line"),
            EditorCommand::PrevChar => write!(f, "prev-char"),
            EditorCommand::NextChar => write!(f, "next-char"),
            EditorCommand::Dot(c) => write!(f, "dot({})", c),
            EditorCommand::MarkStroke => write!(f, "mark-stroke"),
            EditorCommand::MarkLine => write!(f, "mark-line"),
            EditorCommand::MarkRect => write!(f, "mark-rect"),
            EditorCommand::MarkFilledRect => write!(f, "mark-filled-rect"),
            EditorCommand::MarkFill => write!(f, "mark-fill"),
            EditorCommand::Cut => write!(f, "cut"),
            EditorCommand::Copy => write!(f, "copy"),
            EditorCommand::Paste => write!(f, "paste"),
        }
    }
}

impl std::str::FromStr for EditorCommand {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "quit" => Ok(EditorCommand::Quit),
            "legend" => Ok(EditorCommand::Legend),
            "preview" => Ok(EditorCommand::Preview),
            "cancel" => Ok(EditorCommand::Cancel),
            "save" => Ok(EditorCommand::Save),
            "undo" => Ok(EditorCommand::Undo),
            "prev-line" => Ok(EditorCommand::PrevLine),
            "next-line" => Ok(EditorCommand::NextLine),
            "prev-char" => Ok(EditorCommand::PrevChar),
            "next-char" => Ok(EditorCommand::NextChar),
            "mark-stroke" => Ok(EditorCommand::MarkStroke),
            "mark-line" => Ok(EditorCommand::MarkLine),
            "mark-rect" => Ok(EditorCommand::MarkRect),
            "mark-filled-rect" => Ok(EditorCommand::MarkFilledRect),
            "mark-fill" => Ok(EditorCommand::MarkFill),
            "cut" => Ok(EditorCommand::Cut),
            "copy" => Ok(EditorCommand::Copy),
            "paste" => Ok(EditorCommand::Paste),
            s if s.starts_with("dot(") && s.ends_with(")") => {
                let arg = &s[4..s.len() - 1];
                let mut chars = arg.chars();
                match (chars.next(), chars.next()) {
                    (Some(c), None) if !c.is_control() => Ok(EditorCommand::Dot(c)),
                    _ => Err(format!("invalid dot command: {}", s)),
                }
            }
            s if s.starts_with("bg(") && s.ends_with(")") => {
                let arg = &s[3..s.len() - 1];
                let mut chars = arg.chars();
                match (chars.next(), chars.next()) {
                    (Some(c), None) if !c.is_control() => Ok(EditorCommand::Background(c)),
                    _ => Err(format!("invalid bg command: {}", s)),
                }
            }
            s if s.starts_with("scope(") && s.ends_with(")") => {
                let group_name = &s[6..s.len() - 1];
                if group_name.is_empty() {
                    Err(format!("invalid scope command: {}", s))
                } else {
                    Ok(EditorCommand::Scope(group_name.to_owned()))
                }
            }
            _ => Err(format!("unknown command: {}", s)),
        }
    }
}