logviewer/src/tui/log_viewer/mod.rs

414 lines
13 KiB
Rust

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<LogView>,
curr: LogView,
cache: HashMap<Vec<usize>, LogView>,
pub root_stream: Box<dyn LogStream>,
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<dyn LogStream> {
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::<Vec<_>>()
)
.unwrap();
let find_elem_in_stream =
|stream: &dyn LogStream, elem: &Arc<LogEntry>| -> Option<Box<dyn LogStream>> {
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::<LogView>::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::<Vec<_>>()
)
.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<Filter>) {
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::<Vec<_>>()
} else {
Vec::new()
}
}
pub fn items(&mut self, max: usize) -> Option<(Vec<(Arc<LogEntry>, 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<LogEntry>, 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<usize> {
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)))
}
_ => {}
}
}
}