use std::{
fs,
path::PathBuf
};
use cursive_core::{
View,
Printer,
Vec2,
utils::{
markup::StyledString,
lines::spans::{LinesIterator, Row}
},
views::ScrollView,
view::{ScrollStrategy, Scrollable},
theme::{
BaseColor,
Color,
Style,
ColorStyle
}
};
use rust_utils::logging::Log;
use lazy_static::lazy_static;
use regex::Regex;
use unicode_width::UnicodeWidthStr;
use crate::SpannedStrExt;
lazy_static! {
static ref INFO_RE: Regex = Regex::new(r"\[.*INFO\]").unwrap();
static ref DBG_RE: Regex = Regex::new(r"\[.*DEBUG\]").unwrap();
static ref WARN_RE: Regex = Regex::new(r"\[.*WARN\]").unwrap();
static ref ERROR_RE: Regex = Regex::new(r"\[.*ERROR\]").unwrap();
static ref FATAL_RE: Regex = Regex::new(r"\[.*FATAL\]").unwrap();
}
#[derive(Clone)]
pub struct LogView {
path: PathBuf,
content: LogContent
}
impl LogView {
pub fn new<P: Into<PathBuf>>(path: P) -> LogView {
let path = path.into();
let raw_log = fs::read_to_string(&path).unwrap_or_default();
let content = LogContent::new(raw_log);
LogView {
path,
content
}
}
pub fn scroll_to_bottom(self) -> ScrollView<Self> {
self.scrollable().scroll_strategy(ScrollStrategy::StickToBottom)
}
}
impl View for LogView {
fn draw(&self, printer: &Printer) {
for y in 0..printer.size.y {
printer.print_hline((0, y), printer.size.x, " ");
}
self.content.draw(printer);
}
fn required_size(&mut self, bound: Vec2) -> Vec2 {
self.content.fit_to_width(bound.x);
(bound.x, self.content.num_lines()).into()
}
fn layout(&mut self, size: Vec2) {
let raw_log = fs::read_to_string(&self.path).unwrap_or_default();
self.content.set_content(raw_log);
self.content.fit_to_width(size.x);
}
}
impl From<&Log> for LogView {
fn from(log: &Log) -> Self {
let path = if let Some(main_log_path) = log.main_log_path() {
main_log_path
}
else {
log.log_path()
};
Self::new(path)
}
}
#[derive(Clone)]
struct LogContent {
content: StyledString,
rows: Vec<Row>
}
impl LogContent {
fn new(content: String) -> Self {
let content = colorize_log(&content);
LogContent {
content,
rows: Vec::new()
}
}
fn set_content(&mut self, new_content: String) {
if new_content.as_str() != self.content.source() {
self.content = colorize_log(&new_content);
}
}
fn fit_to_width(&mut self, width: usize) {
if width == 0 { return; }
self.rows = LinesIterator::new(self.content.as_spanned_str(), width).collect();
}
fn num_lines(&self) -> usize {
self.rows.len()
}
fn draw(&self, printer: &Printer) {
for (y, row) in self.rows.iter().enumerate() {
let mut x = 0;
for span in row.resolve(self.content.as_spanned_str()) {
printer.with_style(*span.attr, |printer| {
printer.print((x, y), span.content);
x += span.content.width();
});
}
}
}
}
fn colorize_log(log: &str) -> StyledString {
let mut styled_log = StyledString::new();
let mut line_color = Style::from(Color::Light(BaseColor::White));
for log_line in log.lines() {
if INFO_RE.is_match(log_line) {
line_color = Style::from(Color::Dark(BaseColor::Green));
}
else if DBG_RE.is_match(log_line) {
line_color = Style::from(Color::Dark(BaseColor::Cyan));
}
else if WARN_RE.is_match(log_line) {
line_color = Style::from(Color::Light(BaseColor::Yellow));
}
else if ERROR_RE.is_match(log_line) {
line_color = Style::from(Color::Light(BaseColor::Red));
}
else if FATAL_RE.is_match(log_line) {
line_color = Style::from(ColorStyle::new(Color::Light(BaseColor::Red), Color::Dark(BaseColor::Black)));
}
styled_log.append_styled(log_line, line_color);
styled_log.append('\n');
}
styled_log
}