amber 0.6.1

A code search and replace tool
Documentation
extern crate term;

use crate::matcher::Match;
use std::io;
use std::io::Write;
use std::process;
use term::color::Color;
use term::{StderrTerminal, StdoutTerminal};

// ---------------------------------------------------------------------------------------------------------------------
// Console
// ---------------------------------------------------------------------------------------------------------------------

pub enum ConsoleTextKind {
    Filename,
    Text,
    MatchText,
    Other,
    Info,
    Error,
}

pub struct Console {
    pub is_color: bool,
    term_stdout: Box<StdoutTerminal>,
    term_stderr: Box<StderrTerminal>,
    last_color: Color,
}

const CR: u8 = 0x0d;
const LF: u8 = 0x0a;

impl Default for Console {
    fn default() -> Self {
        Self::new()
    }
}

impl Console {
    pub fn new() -> Self {
        Console {
            term_stdout: term::stdout().unwrap_or_else(|| {
                process::exit(1);
            }),
            term_stderr: term::stderr().unwrap_or_else(|| {
                process::exit(1);
            }),
            is_color: true,
            last_color: term::color::BLACK,
        }
    }

    pub fn carriage_return(&mut self) {
        let _ = self.term_stdout.carriage_return();
    }

    pub fn cursor_up(&mut self) {
        let _ = self.term_stdout.cursor_up();
    }

    pub fn delete_line(&mut self) {
        let _ = self.term_stdout.delete_line();
    }

    pub fn write_with_clear(&mut self, kind: ConsoleTextKind, val: &str) {
        self.carriage_return();
        self.delete_line();
        self.write(kind, val);
    }

    pub fn write(&mut self, kind: ConsoleTextKind, val: &str) {
        let color = match kind {
            ConsoleTextKind::Filename => term::color::BRIGHT_GREEN,
            ConsoleTextKind::Text => term::color::WHITE,
            ConsoleTextKind::MatchText => term::color::BRIGHT_YELLOW,
            ConsoleTextKind::Other => term::color::BRIGHT_CYAN,
            ConsoleTextKind::Info => term::color::BRIGHT_CYAN,
            ConsoleTextKind::Error => term::color::BRIGHT_RED,
        };

        match kind {
            ConsoleTextKind::Error => self.write_stderr(val, color),
            ConsoleTextKind::Info => self.write_stderr(val, color),
            _ => self.write_stdout(val, color),
        }
    }

    pub fn flush(&mut self) {
        let _ = io::stdout().flush();
        let _ = io::stderr().flush();
    }

    pub fn reset(&mut self) {
        self.term_stdout.reset().unwrap_or_else(|_| {
            process::exit(1);
        });
        self.term_stderr.reset().unwrap_or_else(|_| {
            process::exit(1);
        });
    }

    pub fn get_line_beg(src: &[u8], beg: usize) -> usize {
        let mut ret = beg;
        while ret > 0 {
            if src[ret] == CR || src[ret] == LF {
                ret += 1;
                break;
            }
            ret -= 1;
        }
        ret
    }

    pub fn get_line_end(src: &[u8], end: usize) -> usize {
        let mut ret = end;
        while src.len() > ret {
            if src[ret] == CR || src[ret] == LF {
                ret -= 1;
                break;
            }
            ret += 1;
        }
        if src.len() <= ret {
            ret = src.len()
        } else {
            ret += 1
        };
        ret
    }

    pub fn write_to_linebreak(&mut self, src: &[u8], beg: usize, end: usize) {
        if beg < end {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[beg..end]));
        }
        self.write(ConsoleTextKind::Text, "\n");
    }

    pub fn write_match_part(&mut self, src: &[u8], m: &Match, beg: usize) {
        if beg < m.beg {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[beg..m.beg]));
        }
        self.write(ConsoleTextKind::MatchText, &String::from_utf8_lossy(&src[m.beg..m.end]));
    }

    pub fn write_match_line(&mut self, src: &[u8], m: &Match) {
        let beg = Console::get_line_beg(src, m.beg);
        let end = Console::get_line_end(src, m.end);

        if beg < m.beg {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[beg..m.beg]));
        }
        self.write(ConsoleTextKind::MatchText, &String::from_utf8_lossy(&src[m.beg..m.end]));
        if m.end < end {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[m.end..end]));
        }
        self.write(ConsoleTextKind::Text, "\n");
    }

    pub fn write_replace_line(&mut self, src: &[u8], m: &Match, rep: &[u8]) {
        let beg = Console::get_line_beg(src, m.beg);
        let end = Console::get_line_end(src, m.end);

        if beg < m.beg {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[beg..m.beg]));
        }
        self.write(ConsoleTextKind::MatchText, &String::from_utf8_lossy(rep));
        if m.end < end {
            self.write(ConsoleTextKind::Text, &String::from_utf8_lossy(&src[m.end..end]));
        }
        self.write(ConsoleTextKind::Text, "\n");
    }

    fn write_stdout(&mut self, val: &str, color: Color) {
        if self.is_color && self.last_color != color {
            self.term_stdout.fg(color).unwrap_or_else(|_| {
                process::exit(1);
            });
            self.last_color = color;
        }

        write!(self.term_stdout, "{}", val).unwrap_or_else(|_| {
            process::exit(1);
        });
    }

    fn write_stderr(&mut self, val: &str, color: Color) {
        if self.is_color && self.last_color != color {
            self.term_stderr.fg(color).unwrap_or_else(|_| {
                process::exit(1);
            });
            self.last_color = color;
        }

        write!(self.term_stderr, "{}", val).unwrap_or_else(|_| {
            process::exit(1);
        });
    }
}