use std::{collections::HashMap, path::PathBuf, sync::Arc}; use crate::tui::{ filter::Filter, log_viewer::{ filters::Filters, input::{FieldMatcher, InputState, InputTarget}, view::LogView, }, model::{LogEntry, SpanDescriptor, id}, processing::Cursor, widgets::{fieldtree, last_error::LastError}, }; use dumpster::sync::Gc; pub mod filters; pub mod input; pub mod view; pub struct LogViewer { cache: HashMap, pub view: LogView, pub last_height: usize, pub last_offset: usize, pub last_fields_offset: usize, pub last_fields_height: usize, pub filters: Filters, pub input_state: InputState, pub field_state: fieldtree::State, } impl LogViewer { pub fn new(start: Gc, filters_path: PathBuf, error: LastError) -> Self { let filters = Filters::new(Some(filters_path), error); Self { view: LogView { cursor: Cursor::new(start), selection_offset: 0, }, cache: HashMap::new(), last_height: 0, last_offset: 0, last_fields_offset: 0, last_fields_height: 0, filters, input_state: InputState::None, field_state: fieldtree::State::default(), } } pub fn get_selected_field(&self) -> Option<(SpanDescriptor, String, String)> { let (span_idx, name_idx) = self.field_state.get()?; let entry = self.selected().map(|(s, _)| s)?; let (span, fields) = entry.spans().named().nth(span_idx)?; let (name, value) = fields.fields.iter().nth(name_idx)?; Some((span, name.clone(), value.clone())) } pub fn add_filter(&mut self, filter: Arc) { self.filters.push(Arc::clone(&filter)); if !self.view.cursor.update_with_parents(&self.filters) { panic!("no log entries"); } } pub fn update_num_items(&mut self, num_visible_items: usize) { while self.view.selection_offset >= num_visible_items { if self.view.selection_offset == 0 { break; } self.view.cursor.next(&self.filters); self.view.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 { children, .. } => children.last_child.as_ref().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<(Gc, usize)>, usize)> { let mut temp_iter = self.view.cursor.clone(); let mut res = Vec::new(); for _ in 0..max { if !temp_iter.next(&self.filters) { break; } // TODO: inline depth res.push((temp_iter.curr(), temp_iter.inline_depth())); } if !res.is_empty() && self.view.selection_offset > res.len() - 1 { self.view.selection_offset = res.len() - 1; } Some((res, self.view.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.view.selection_offset == row_in_list { self.input_state = InputState::None; self.enter(); } else { self.view.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))); todo!() // self.footer_list.select(Some(row_in_fields)); } } } pub fn selected(&self) -> Option<(Gc, usize)> { self.view.selected(&self.filters) } pub fn prev(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.field_state.up(); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { if self.view.selection_offset == 0 { self.view.cursor.prev(&self.filters); } else { self.view.selection_offset -= 1; } self.input_state = InputState::None; } } } pub fn next(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.field_state.down(); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { self.view.selection_offset += 1; self.input_state = InputState::None; } } } pub fn page_down(&mut self) { self.view.selection_offset += self.last_height; self.input_state.reset(); } pub fn page_up(&mut self) { for _ in 0..self.last_height { if self.view.selection_offset == 0 { let _ = self.view.cursor.prev(&self.filters); } else { self.view.selection_offset -= 1; } } self.input_state.reset(); } pub fn home(&mut self) { match self.input_state { InputState::Target(InputTarget::Fields(..)) => { self.field_state.home(); self.input_state = InputState::Target(InputTarget::Fields(None)); } _ => { self.view.selection_offset = 0; while self.view.cursor.prev(&self.filters) {} self.input_state = InputState::None; } } } fn update_offset_from_cache(&mut self) { let cache_key = self.view.cursor.parent().as_ref().map(id).unwrap_or(0); self.view.selection_offset = 0; if let Some(offset) = self.cache.get(&cache_key) { for _ in 0..*offset { self.view.selection_offset += 1; self.view.cursor.prev(&self.filters); } } } fn add_to_cache(&mut self) { let cache_key = self.view.cursor.parent().as_ref().map(id).unwrap_or(0); self.cache.insert(cache_key, self.view.selection_offset); } pub fn back(&mut self) { match self.input_state { InputState::None => { self.add_to_cache(); if self.view.cursor.exit(&self.filters) { self.update_offset_from_cache(); self.view.cursor.prev(&self.filters); } // self.cache.insert(self.path(), self.curr.clone()); self.input_state.reset(); } InputState::Target(InputTarget::Fields(None)) => { self.field_state.left(); } _ => {} } } pub fn undo(&mut self) { self.filters.undo(); } pub fn redo(&mut self) { self.filters.redo(); } pub fn right(&mut self) { match self.input_state { InputState::None => { self.enter(); } InputState::Target(InputTarget::Fields(None)) => { self.field_state.right(); } _ => {} } } pub fn enter(&mut self) { match self.input_state { InputState::None => { self.add_to_cache(); let orig = self.view.cursor.clone(); for _ in 0..(self.view.selection_offset + 1) { self.view.cursor.next(&self.filters); } if self.view.cursor.enter(&self.filters) { self.update_offset_from_cache(); } else { self.view.cursor = orig; } } InputState::Target(InputTarget::Fields(None)) => { self.input_state = InputState::Target(InputTarget::Fields(Some(FieldMatcher::EqualTo))) } _ => {} } } }