mudrs-milk 0.0.1

WIP Mud Client
Documentation
use std::{
    io::Write,
    mem,
};

use alacritty_terminal::ansi::{
    Attr,
    Color,
    Handler,
    NamedColor,
    Processor,
};
use tui::{
    style,
    style::Style,
    text::Span,
};

use crate::text::{
    Line,
    Text,
};

pub struct AnsiHandler {
    lines: Vec<Line>,
    current: Text,

    // Two characters of look-behind for mid-line CR handling
    // We don't allow those, so we'll insert a newline if we encounter one.
    prev: char,
    prevprev: char,
}

pub struct AnsiProcessor {
    handler: AnsiHandler,
    processor: Processor,
}

impl AnsiProcessor {
    pub fn new(style: Style) -> Self {
        AnsiProcessor {
            processor: Processor::new(),
            handler: AnsiHandler::new(style),
        }
    }

    pub fn append(&mut self, bytes: &[u8]) {
        let handler = &mut self.handler;

        let processor = &mut self.processor;
        for b in bytes {
            processor.advance(handler, *b)
        }
    }

    pub fn take(&mut self) -> (Vec<Line>, Text) {
        let cap = self.handler.lines.capacity();
        let lines = mem::replace(&mut self.handler.lines, Vec::with_capacity(cap));
        let current = mem::replace(&mut self.handler.current, Text::new());
        return (lines, current);
    }
}

struct NopWriter;

impl Write for NopWriter {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        Ok(buf.len())
    }
    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

impl AnsiHandler {
    fn new(style: Style) -> AnsiHandler {
        let mut handler = AnsiHandler {
            lines: Vec::with_capacity(32),
            current: Text::with_style(style),
            prev: 0 as char,
            prevprev: 0 as char,
        };
        handler.new_line();
        handler
    }

    fn new_line(&mut self) {
        let line = self.current.swap_complete(Line::new());
        self.lines.push(line);
    }

    fn apply_style(&mut self, style: Style) {
        self.current.set_style(style);
    }

    fn current_span_mut(&mut self) -> &mut Span<'static> {
        self.current.current_span_mut()
    }

    fn shift_prev(&mut self, c: char) {
        self.prevprev = self.prev;
        self.prev = c;
    }
}

impl Handler for AnsiHandler {
    fn input(&mut self, c: char) {
        // If we got a carriage return *without* a linefeed, go ahead and insert
        // one.
        if self.prev == '\r' && self.prevprev != '\n' {
            self.new_line();
        }
        self.shift_prev(c);
        self.current_span_mut().content.to_mut().push(c)
    }

    fn newline(&mut self) {
        self.linefeed();
    }

    fn linefeed(&mut self) {
        self.shift_prev('\n');
        self.new_line();
    }

    fn carriage_return(&mut self) {
        self.shift_prev('\r');
    }

    fn terminal_attribute(&mut self, attr: Attr) {
        let mut style = self.current.style();
        match attr {
            Attr::Reset => style = Style::reset(),
            Attr::Foreground(color) => {
                to_tui_color(color).map(|color| style.fg = color.into());
            }
            Attr::Background(color) => {
                to_tui_color(color).map(|color| style.bg = color.into());
            }
            Attr::Bold => style = style.add_modifier(tui::style::Modifier::BOLD),
            Attr::Italic => style = style.add_modifier(tui::style::Modifier::ITALIC),
            Attr::Dim => style = style.add_modifier(tui::style::Modifier::DIM),
            Attr::Underline => style = style.add_modifier(tui::style::Modifier::UNDERLINED),
            Attr::BlinkSlow => style = style.add_modifier(tui::style::Modifier::SLOW_BLINK),
            Attr::BlinkFast => style = style.add_modifier(tui::style::Modifier::RAPID_BLINK),
            Attr::Reverse => style = style.add_modifier(tui::style::Modifier::REVERSED),
            Attr::Hidden => style = style.add_modifier(tui::style::Modifier::HIDDEN),
            Attr::Strike => style = style.add_modifier(tui::style::Modifier::CROSSED_OUT),
            Attr::CancelBold => style = style.remove_modifier(tui::style::Modifier::BOLD),
            Attr::CancelItalic => style = style.remove_modifier(tui::style::Modifier::ITALIC),
            Attr::CancelBoldDim => {
                style = style
                    .remove_modifier(tui::style::Modifier::DIM)
                    .remove_modifier(tui::style::Modifier::BOLD)
            }
            Attr::CancelUnderline => {
                style = style.remove_modifier(tui::style::Modifier::UNDERLINED)
            }
            Attr::CancelBlink => {
                style = style
                    .remove_modifier(tui::style::Modifier::SLOW_BLINK)
                    .remove_modifier(tui::style::Modifier::RAPID_BLINK);
            }
            Attr::CancelReverse => style = style.remove_modifier(tui::style::Modifier::REVERSED),
            Attr::CancelHidden => style = style.remove_modifier(tui::style::Modifier::HIDDEN),
            Attr::CancelStrike => style = style.remove_modifier(tui::style::Modifier::CROSSED_OUT),
            _ => {}
        }
        self.apply_style(style)
    }
}

fn to_tui_color(color: Color) -> Option<style::Color> {
    let named: NamedColor = match color {
        Color::Named(named) => named,
        Color::Indexed(id) => return style::Color::Indexed(id).into(),
        Color::Spec(rgb) => return style::Color::Rgb(rgb.r, rgb.g, rgb.b).into(),
    };

    Some(match named {
        NamedColor::BrightBlack => style::Color::DarkGray,
        NamedColor::Black => style::Color::Black,
        NamedColor::BrightRed => style::Color::LightRed,
        NamedColor::BrightWhite => style::Color::White,
        NamedColor::BrightGreen => style::Color::LightGreen,
        NamedColor::BrightYellow => style::Color::LightYellow,
        NamedColor::BrightBlue => style::Color::LightBlue,
        NamedColor::BrightMagenta => style::Color::LightMagenta,
        NamedColor::BrightCyan => style::Color::LightCyan,
        NamedColor::Red => style::Color::Red,
        NamedColor::White => style::Color::Gray,
        NamedColor::Green => style::Color::Green,
        NamedColor::Yellow => style::Color::Yellow,
        NamedColor::Blue => style::Color::Blue,
        NamedColor::Magenta => style::Color::Magenta,
        NamedColor::Cyan => style::Color::Cyan,
        _ => return None,
    })
}