301 lines
8.1 KiB
Rust
301 lines
8.1 KiB
Rust
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<dyn LogStream>,
|
|
selection_offset: usize,
|
|
}
|
|
|
|
impl LogView {
|
|
pub fn selected(&self) -> Option<Rc<LogEntry>> {
|
|
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<LogView>,
|
|
curr: LogView,
|
|
cache: HashMap<Vec<usize>, LogView>,
|
|
filters: Vec<Rc<Filter>>,
|
|
|
|
pub root_stream: Box<dyn LogStream>,
|
|
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<dyn LogStream> {
|
|
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<Filter>) {
|
|
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<LogEntry>,
|
|
) -> Option<Box<dyn LogStream>> {
|
|
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::<LogView>::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::<Vec<_>>()
|
|
} else {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
pub fn items(&self, max: usize) -> Option<(Vec<Rc<LogEntry>>, 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<Rc<LogEntry>> {
|
|
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<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.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();
|
|
}
|
|
}
|
|
}
|