use termion::event::Key; use failure::Fail; use chrono::{DateTime, Local}; use crate::term; use crate::widget::Widget; use crate::listview::{ListView, Listable}; use crate::fail::{HResult, HError}; use crate::dirty::Dirtyable; pub type LogView = ListView>; #[derive(Debug)] pub struct LogEntry { description: String, content: Option, lines: usize, folded: bool } impl Foldable for LogEntry { fn description(&self) -> &String { &self.description } fn content(&self) -> Option<&String> { self.content.as_ref() } fn lines(&self) -> usize { if self.is_folded() { 1 } else { self.lines } } fn toggle_fold(&mut self) { self.folded = !self.folded; } fn is_folded(&self) -> bool { self.folded } } impl From<&HError> for LogEntry { fn from(from: &HError) -> LogEntry { let time: DateTime = Local::now(); let description = format!("{}{}{}: {}", term::color_green(), time.format("%F %R"), term::color_red(), from).lines().take(1).collect(); let mut content = format!("{}{}{}: {}\n", term::color_green(), time.format("%F %R"), term::color_red(), from); if let Some(cause) = from.cause() { content += &format!("{}\n", cause); } if let Some(backtrace) = from.backtrace() { content += &format!("{}\n", backtrace); } let lines = content.lines().count(); LogEntry { description: description, content: Some(content), lines: lines, folded: true } } } pub trait FoldableWidgetExt { fn on_refresh(&mut self) -> HResult<()> { Ok(()) } } impl FoldableWidgetExt for ListView> { fn on_refresh(&mut self) -> HResult<()> { if self.content.refresh_logs()? > 0 { self.core.set_dirty(); } Ok(()) } } trait LogList { fn refresh_logs(&mut self) -> HResult; } impl LogList for Vec { fn refresh_logs(&mut self) -> HResult { let logs = crate::fail::get_logs()?; let mut logentries = logs.into_iter().map(|log| { LogEntry::from(log) }).collect::>(); let n = logentries.len(); self.append(&mut logentries); Ok(n) } } pub trait Foldable { fn description(&self) -> &String; fn content(&self) -> Option<&String>; fn lines(&self) -> usize; fn toggle_fold(&mut self); fn is_folded(&self) -> bool; fn text(&self) -> &String { if !self.is_folded() && self.content().is_some() { self.content().unwrap() } else { self.description() } } fn render_description(&self) -> String { self.description().to_string() } fn render_content(&self) -> Vec { if let Some(content) = self.content() { content .lines() .map(|line| line.to_string()) .collect() } else { vec![self.render_description()] } } fn render(&self) -> Vec { if self.is_folded() { vec![self.render_description()] } else { self.render_content() } } } impl ListView> where ListView>: FoldableWidgetExt { fn toggle_fold(&mut self) -> HResult<()> { let fold = self.current_fold(); let fold_pos = self.fold_start_pos(fold); self.content[fold].toggle_fold(); if self.content[fold].is_folded() { self.set_selection(fold_pos); } self.core.set_dirty(); Ok(()) } fn fold_start_pos(&self, fold: usize) -> usize { self.content .iter() .take(fold) .fold(0, |pos, foldable| { pos + (foldable.lines()) }) } fn current_fold(&self) -> usize { let pos = self.get_selection(); let fold_lines = self .content .iter() .map(|f| f.lines()) .collect::>(); fold_lines .iter() .enumerate() .fold((0, None), |(lines, fold_pos), (i, current_fold_lines)| { if fold_pos.is_some() { (lines, fold_pos) } else { if lines + current_fold_lines > pos { (lines, Some(i)) } else { (lines + current_fold_lines, None) } }}).1.unwrap() } } impl Listable for ListView> where ListView>: FoldableWidgetExt { fn len(&self) -> usize { self.content.iter().map(|f| f.lines()).sum() } fn render(&self) -> Vec { let (xsize, _) = self.core.coordinates.size_u(); self.content .iter() .map(|foldable| foldable .render() .iter() .map(|line| term::sized_string_u(line, xsize)) .collect::>()) .flatten() .collect() } fn on_refresh(&mut self) -> HResult<()> { FoldableWidgetExt::on_refresh(self) } fn on_key(&mut self, key: Key) -> HResult<()> { match key { Key::Up | Key::Char('p') => self.move_up(), Key::Char('P') => for _ in 0..10 { self.move_up() }, Key::Char('N') => for _ in 0..10 { self.move_down() }, Key::Down | Key::Char('n') => self.move_down(), Key::Char('t') => self.toggle_fold()?, Key::Char('l') => self.popup_finnished()?, _ => {} } Ok(()) } }