use std::{collections::HashMap, iter, mem, rc::Rc}; use crate::tui::{ filter::Filter, model::LogEntry, processing::{IntoLogStream, LogStream}, }; use tui_widget_list::ListState; pub struct LogView { iter: Box, selection_offset: usize, } impl LogView { pub fn selected(&self) -> Option> { let mut temp_iter = self.iter.clone(); for _ in 0..self.selection_offset { let _ = temp_iter.next()?; } temp_iter.next() } } impl Clone for LogView { fn clone(&self) -> Self { Self { iter: self.iter.clone(), selection_offset: self.selection_offset.clone(), } } } pub struct LogViewer { stack: Vec, curr: LogView, cache: HashMap, LogView>, filters: Vec>, pub root_stream: Box, pub last_height: usize, pub footer_selected: bool, pub footer_list: ListState, } impl LogViewer { pub fn new(stream: impl LogStream) -> Self { Self { stack: Vec::new(), curr: LogView { iter: stream.clone(), selection_offset: 0, }, root_stream: stream.clone(), cache: HashMap::new(), footer_list: ListState::default(), footer_selected: false, last_height: 0, filters: Vec::new(), } } pub fn filtered_root_stream(&self) -> Box { let mut curr = self.root_stream.clone(); for filter in &self.filters { curr = Box::new(curr.filter(Rc::clone(filter))); } curr } pub fn add_filter(&mut self, filter: Rc) { self.filters.push(Rc::clone(&filter)); self.cache.clear(); let offsets_list: Vec<_> = self .stack .iter() .map(|i| (i.selected(), i.selection_offset)) .chain(iter::once(( self.curr.selected(), self.curr.selection_offset, ))) .collect(); fn find_elem_in_stream( stream: &dyn LogStream, elem: &Rc, ) -> Option> { let mut temp_stream = stream.clone(); let mut max = 100usize; while let Some(curr) = temp_stream.next() { if Rc::ptr_eq(&curr, elem) { return Some(temp_stream); } if max == 0 { break; } max -= 1; } None } let mut current_stream = self.filtered_root_stream(); let mut new_stack = Vec::::new(); 'outer: for (elem, old_offset) in offsets_list { let Some(elem) = elem else { break; }; // If the value we're looking for is removed by the filter, // we'll have a hard time finding it so quit if filter.removes(&elem) { break; } // find the nearest stream in which this element can be found let mut curr = current_stream.as_ref(); let mut parents = new_stack.iter().rev(); let mut found_in_toplevel = true; let mut stream = loop { if let Some(stream) = find_elem_in_stream(curr, &elem) { break stream; } found_in_toplevel = false; if let Some(parent) = parents.next() { curr = parent.iter.as_ref(); continue; } break 'outer; }; let offset = if found_in_toplevel { let mut offset = 0; for _ in 0..old_offset { if stream.prev().is_none() { break; } offset += 1; } offset } else { 0 }; current_stream = stream.clone(); new_stack.push(LogView { iter: stream, selection_offset: offset, }); } // Take the top of the stack as `curr`, unless // the new stack is empty, then just reset the current view. if let Some(curr) = new_stack.pop() { self.curr = curr; } else { self.curr = LogView { iter: self.filtered_root_stream().clone(), selection_offset: 0, }; } self.stack = new_stack; } pub fn update_num_items(&mut self, num_visible_items: usize) { while self.curr.selection_offset >= num_visible_items { if self.curr.selection_offset == 0 { break; } self.curr.iter.next(); self.curr.selection_offset -= 1; } self.last_height = num_visible_items; } pub fn footer_fields(&self) -> Vec<(String, serde_json::Value)> { if let Some(selected) = self.selected() { selected.all_fields().fields.into_iter().collect::>() } else { Vec::new() } } pub fn items(&self, max: usize) -> Option<(Vec>, usize)> { let mut temp_iter = self.curr.iter.clone(); let mut res = Vec::new(); for _ in 0..max { let Some(i) = temp_iter.next() else { break; }; res.push(i); } Some((res, self.curr.selection_offset)) } pub fn selected(&self) -> Option> { self.curr.selected() } fn update_footer_select(&mut self) { self.footer_list.select(Some(0)); } pub fn prev(&mut self) { if self.footer_selected { self.footer_list.previous(); } else { if self.curr.selection_offset == 0 { let _ = self.curr.iter.prev(); } else { self.curr.selection_offset -= 1; } self.update_footer_select(); } } pub fn next(&mut self) { if self.footer_selected { self.footer_list.next(); } else { self.curr.selection_offset += 1; self.update_footer_select(); } } pub fn page_down(&mut self) { self.curr.selection_offset += self.last_height; self.footer_selected = false; self.update_footer_select(); } pub fn page_up(&mut self) { for _ in 0..self.last_height { if self.curr.selection_offset == 0 { let _ = self.curr.iter.prev(); } else { self.curr.selection_offset -= 1; } } self.footer_selected = false; self.update_footer_select(); } pub fn home(&mut self) { if self.footer_selected { self.footer_list.select(Some(0)); } else { self.curr.selection_offset = 0; while self.curr.iter.prev().is_some() {} self.update_footer_select(); } } pub fn path(&self) -> Vec { self.stack.iter().map(|i| i.selection_offset).collect() } pub fn back(&mut self) { self.cache.insert(self.path(), self.curr.clone()); if let Some(stack) = self.stack.pop() { self.curr = stack; } self.footer_selected = false; self.update_footer_select(); } pub fn switch_focus(&mut self) { self.footer_selected = !self.footer_selected; } pub fn enter(&mut self) { if !self.footer_selected { let Some(s) = self.selected() else { return; }; let Some(i) = s.from_start() else { return; }; self.stack.push(mem::replace( &mut self.curr, LogView { iter: Box::new(i), selection_offset: 0, }, )); if let Some(cached_view) = self.cache.get(&self.path()) { self.curr = cached_view.clone(); } self.update_footer_select(); } } }