use std::{collections::HashMap, fs::File, io::Write, iter, mem, sync::Arc}; use crate::tui::{ filter::Filter, log_viewer::{ filters::Filters, input::{FieldMatcher, InputState, InputTarget}, view::LogView, }, model::LogEntry, processing::{FilterList, IntoLogStream, LogStream}, }; use tui_widget_list::ListState; pub mod filters; pub mod input; pub mod view; pub struct LogViewer { pub stack: Vec, curr: LogView, cache: HashMap, LogView>, pub root_stream: Box, pub last_height: usize, pub last_offset: usize, pub last_fields_offset: usize, pub last_fields_height: usize, pub footer_list: ListState, filters: Filters, pub input_state: InputState, } 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(), last_height: 0, last_offset: 0, last_fields_offset: 0, last_fields_height: 0, filters: Filters::new(), input_state: InputState::None, } } pub fn filtered_root_stream(&self) -> Box { self.root_stream.with_filters(self.filters.get()) } fn update_filters(&mut self, old_filters: &FilterList) { self.cache.clear(); let offsets_list: Vec<_> = self .stack .iter() .map(|i| (i.selected(old_filters), i.selection_offset)) .chain(iter::once(( self.curr.selected(old_filters), self.curr.selection_offset, ))) .collect(); let mut log = File::options() .create(true) .append(true) .open("./log") .unwrap(); writeln!(&mut log, "active filters: {:?}", self.filters.get()).unwrap(); writeln!( &mut log, "{:?}", offsets_list.iter().map(|i| i.1).collect::>() ) .unwrap(); let find_elem_in_stream = |stream: &dyn LogStream, elem: &Arc| -> Option> { let mut temp_stream = stream.clone(); while let Some((curr, _)) = temp_stream.next(self.filters.get()) { if Arc::ptr_eq(&curr, elem) { return Some(temp_stream); } } None }; let mut current_stream = self.filtered_root_stream(); let mut new_stack = Vec::::new(); let mut missing = false; for (elem, old_offset) in offsets_list { let Some((elem, inline_depth)) = elem else { writeln!(&mut log, "no elem").unwrap(); break; }; writeln!( &mut log, "reconstruction {:?} prev at {old_offset:?}", elem.message_or_name() ) .unwrap(); // 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 { writeln!(&mut log, "find in stream").unwrap(); if let Some(stream) = find_elem_in_stream(curr, &elem) { writeln!(&mut log, "found (toplevel={found_in_toplevel})").unwrap(); break stream; } found_in_toplevel = false; if let Some(parent) = parents.next() { writeln!(&mut log, "searching parent").unwrap(); curr = parent.iter.as_ref(); continue; } writeln!(&mut log, "no more parents").unwrap(); missing = true; // TODO: better guess at how far down we need to go // now its just 0 break current_stream; }; let offset = if found_in_toplevel { let mut offset = 0; for _ in 0..old_offset { if stream.prev(self.filters.get()).is_none() { break; } offset += 1; } offset } else { 0 }; writeln!(&mut log, "new offset: {offset:?}").unwrap(); let nested_stream = elem.from_start(inline_depth); new_stack.push(LogView { iter: stream, selection_offset: offset, }); // If we found a missing entry, don't try deeper entries, // they won't exist if missing { break; } else if let Some(nested_stream) = nested_stream { current_stream = nested_stream.with_filters(self.filters.get()); } else { break; } } writeln!( &mut log, "{:?}", new_stack .iter() .map(|i| ( i.iter .clone() .next(self.filters.get()) .map(|i| i.0.message_or_name()), i.selection_offset )) .collect::>() ) .unwrap(); // 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() { writeln!(&mut log, "popped new curr off stack").unwrap(); self.curr = curr; } else { writeln!(&mut log, "reconstructing root, somehow").unwrap(); self.curr = LogView { iter: self.filtered_root_stream().clone(), selection_offset: self.stack.first().unwrap_or(&self.curr).selection_offset, }; } self.stack = new_stack; } pub fn add_filter(&mut self, filter: Arc) { let old_filters = self.filters.clone(); self.filters.push(Arc::clone(&filter)); self.update_filters(old_filters.get()); } 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.filters.get()); self.curr.selection_offset -= 1; } self.last_height = num_visible_items; } pub fn footer_fields(&self) -> Vec<(String, String)> { if let Some((selected, _)) = self.selected() { let ret = match selected.as_ref() { LogEntry::Single { .. } => Default::default(), LogEntry::Sub { sub_entries, .. } => sub_entries.last().and_then(|i| { i.all_fields() .get_key_value("return") .map(|(k, v)| (k.clone(), v.clone())) }), }; selected .all_relevant_fields() .fields .into_iter() .chain(ret) .collect::>() } else { Vec::new() } } pub fn items(&mut self, max: usize) -> Option<(Vec<(Arc, usize)>, usize)> { let mut temp_iter = self.curr.iter.clone(); let mut res = Vec::new(); for _ in 0..max { let Some(i) = temp_iter.next(self.filters.get()) else { break; }; res.push(i); } if res.len() > 0 && self.curr.selection_offset > res.len() - 1 { self.curr.selection_offset = res.len() - 1; } Some((res, self.curr.selection_offset)) } pub fn click(&mut self, row: u16) { if row as usize >= self.last_offset { let row_in_list = row as usize - self.last_offset; if row_in_list < self.last_height { if self.curr.selection_offset == row_in_list { self.input_state = InputState::None; self.enter(); } else { self.curr.selection_offset = row_in_list; self.input_state = InputState::Target(InputTarget::This); } } } if row as usize >= self.last_fields_offset { let row_in_fields = row as usize - self.last_fields_offset; if row_in_fields < self.last_fields_height { self.input_state = InputState::Target(InputTarget::Fields(Some(FieldMatcher::EqualTo))); self.footer_list.select(Some(row_in_fields)); } } } pub fn selected(&self) -> Option<(Arc, usize)> { self.curr.selected(self.filters.get()) } pub fn prev(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.footer_list.previous(); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { if self.curr.selection_offset == 0 { let _ = self.curr.iter.prev(self.filters.get()); } else { self.curr.selection_offset -= 1; } self.input_state = InputState::None; } } } pub fn next(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.footer_list.next(); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { self.curr.selection_offset += 1; self.input_state = InputState::None; } } } pub fn page_down(&mut self) { self.curr.selection_offset += self.last_height; self.input_state.reset(); } pub fn page_up(&mut self) { for _ in 0..self.last_height { if self.curr.selection_offset == 0 { let _ = self.curr.iter.prev(self.filters.get()); } else { self.curr.selection_offset -= 1; } } self.input_state.reset(); } pub fn home(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.footer_list.select(Some(0)); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { self.curr.selection_offset = 0; while self.curr.iter.prev(self.filters.get()).is_some() {} self.input_state = InputState::None; } } } 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.input_state.reset(); } pub fn undo(&mut self) { let old_filters = self.filters.clone(); self.filters.undo(); self.update_filters(old_filters.get()); } pub fn redo(&mut self) { let old_filters = self.filters.clone(); self.filters.redo(); self.update_filters(old_filters.get()); } pub fn enter(&mut self) { match self.input_state { InputState::None => { let Some((s, _)) = self.selected() else { return; }; let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else { return; }; if i.clone().next(self.filters.get()).is_none() { return; } if i.clone() .next(self.filters.get()) .is_some_and(|(i, _)| i.is_return()) { return; } self.stack.push(mem::replace( &mut self.curr, LogView { iter: i, selection_offset: 0, }, )); if let Some(cached_view) = self.cache.get(&self.path()) { self.curr = cached_view.clone(); } } InputState::Target(InputTarget::Fields(None)) => { self.input_state = InputState::Target(InputTarget::Fields(Some(FieldMatcher::EqualTo))) } _ => {} } } }