292 lines
8.9 KiB
Rust
292 lines
8.9 KiB
Rust
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<usize, usize>,
|
|
|
|
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<LogEntry>, 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<Filter>) {
|
|
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::<Vec<_>>()
|
|
// } else {
|
|
// Vec::new()
|
|
// }
|
|
// }
|
|
|
|
pub fn items(&mut self, max: usize) -> Option<(Vec<(Gc<LogEntry>, 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<LogEntry>, 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)))
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|