evar 0.1.1

Modern ergonomic math calculator inspired by eva
use std::borrow::Cow;

use crate::models::Token;
use colored::Colorize;
use logos::Logos;
use rustyline::{
    Completer, Config, Editor, Helper, Highlighter, Hinter, Validator, error::ReadlineError,
    highlight::Highlighter, hint::HistoryHinter, history::FileHistory,
    validate::MatchingBracketValidator,
};

#[derive(Helper, Completer, Hinter, Validator, Highlighter)]
struct RustyLineHelper {
    #[rustyline(Validator)]
    validator: MatchingBracketValidator,
    #[rustyline(Hinter)]
    hinter: HistoryHinter,
    #[rustyline(Highlighter)]
    highlighter: SevaHighlighter,
}

struct SevaHighlighter;

impl Highlighter for SevaHighlighter {
    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> std::borrow::Cow<'l, str> {
        let tokens = Token::lexer(line).spanned();

        let highlighted_line = tokens.fold(String::new(), |acc, (lex_result, span)| {
            acc + &{
                match lex_result {
                    Err(_) => format!("{}", line[span].truecolor(237, 135, 150)),
                    Ok(token) => match token {
                        Token::Int(_) | Token::Float(_) => {
                            format!("{}", line[span].truecolor(245, 169, 127))
                        }
                        Token::Ident(_) => format!("{}", line[span].truecolor(138, 173, 244)),
                        Token::Plus
                        | Token::Minus
                        | Token::Asterisk
                        | Token::Slash
                        | Token::Percent
                        | Token::Caret
                        | Token::Exclamation => format!("{}", line[span].truecolor(125, 196, 228)),
                        Token::LParen | Token::RParen => {
                            format!("{}", line[span].truecolor(238, 212, 159))
                        }
                        Token::Let => format!("{}", line[span].truecolor(198, 160, 246)),
                        Token::Equal => format!("{}", line[span].truecolor(125, 196, 228)),
                        _ => line[span].to_string(),
                    },
                }
            }
        });

        highlighted_line.into()
    }
    fn highlight_char(
        &self,
        _line: &str,
        _pos: usize,
        kind: rustyline::highlight::CmdKind,
    ) -> bool {
        use rustyline::highlight::CmdKind;
        kind != CmdKind::MoveCursor && kind != CmdKind::ForcedRefresh
    }
    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
        format!("{}", hint.truecolor(91, 96, 120)).into()
    }
    fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
        &'s self,
        prompt: &'p str,
        _default: bool,
    ) -> Cow<'b, str> {
        format!("{}", prompt.truecolor(166, 218, 149)).into()
    }
}

pub struct SevaEditor(Editor<RustyLineHelper, FileHistory>);

impl SevaEditor {
    pub fn new(no_color: bool) -> SevaEditor {
        if no_color {
            colored::control::set_override(false);
        }

        let editor_config = Config::builder()
            .auto_add_history(true)
            .completion_type(rustyline::CompletionType::List)
            .bell_style(rustyline::config::BellStyle::None)
            .build();

        let helper = RustyLineHelper {
            validator: MatchingBracketValidator::new(),
            hinter: HistoryHinter::new(),
            highlighter: SevaHighlighter,
        };

        let mut editor = Editor::with_config(editor_config).expect("failed to create editor");
        editor.set_helper(Some(helper));
        editor.bind_sequence(rustyline::KeyEvent::ctrl('f'), rustyline::Cmd::CompleteHint);
        SevaEditor(editor)
    }

    pub fn readline(&mut self) -> Result<String, ReadlineError> {
        self.0.readline("> ")
    }

    pub fn load_history(&mut self, path: &std::path::Path) -> Result<(), crate::models::SevaError> {
        self.0.load_history(path).map_err(|e| e.into())
    }

    pub fn save_history(&mut self, path: &std::path::Path) -> Result<(), crate::models::SevaError> {
        self.0.save_history(path).map_err(|e| e.into())
    }
}