new streams

This commit is contained in:
Jana Dönszelmann 2026-02-24 12:41:00 +01:00
parent 3963fc50c3
commit c73be7166f
No known key found for this signature in database
13 changed files with 748 additions and 174 deletions

View file

@ -1,88 +1,208 @@
use std::{collections::HashMap, mem, rc::Rc};
use std::{collections::HashMap, iter, mem, rc::Rc};
use crate::tui::{
filter::Filter,
model::LogEntry,
reader::{FilterAdapter, LogfileReader},
processing::{IntoLogStream, LogStream},
};
use tui_widget_list::ListState;
#[derive(Default, Clone)]
pub struct LogView {
first_item: usize,
selected: usize,
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() -> Self {
pub fn new(stream: impl LogStream) -> Self {
Self {
stack: Vec::new(),
curr: LogView::default(),
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.selected >= self.curr.first_item + num_visible_items {
self.curr.first_item += 1;
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, file: &mut LogfileReader) -> Vec<(String, serde_json::Value)> {
if let Some(selected) = self.selected(file) {
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,
file: &mut LogfileReader,
max: usize,
) -> Option<(Vec<Rc<LogEntry>>, usize, usize)> {
let items: Vec<_> = if self.stack.is_empty() {
file.iter_from(self.curr.first_item).take(max).collect()
} else {
let mut stack = self.stack.iter();
let first = stack.next().unwrap();
let mut curr_log_entry = file.iter_from(first.selected).next()?;
for elem in stack {
curr_log_entry = curr_log_entry.get(elem.selected)?;
}
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);
}
match curr_log_entry.as_ref() {
LogEntry::Single { .. } => return None,
LogEntry::Sub { sub_entries, .. } => {
FilterAdapter::new(file, &sub_entries[self.curr.first_item..])
.take(max)
.cloned()
.collect()
}
}
};
Some((items, self.curr.first_item, self.curr.selected))
Some((res, self.curr.selection_offset))
}
pub fn selected(&self, file: &mut LogfileReader) -> Option<Rc<LogEntry>> {
self.items(file, self.curr.selected - self.curr.first_item + 1)?
.0
.get(self.curr.selected - self.curr.first_item)
.cloned()
pub fn selected(&self) -> Option<Rc<LogEntry>> {
self.curr.selected()
}
fn update_footer_select(&mut self) {
@ -93,8 +213,11 @@ impl LogViewer {
if self.footer_selected {
self.footer_list.previous();
} else {
self.curr.selected = self.curr.selected.saturating_sub(1);
self.curr.first_item = self.curr.first_item.min(self.curr.selected);
if self.curr.selection_offset == 0 {
let _ = self.curr.iter.prev();
} else {
self.curr.selection_offset -= 1;
}
self.update_footer_select();
}
}
@ -103,20 +226,25 @@ impl LogViewer {
if self.footer_selected {
self.footer_list.next();
} else {
self.curr.selected += 1;
self.curr.selection_offset += 1;
self.update_footer_select();
}
}
pub fn page_down(&mut self) {
self.curr.selected += self.last_height;
self.curr.selection_offset += self.last_height;
self.footer_selected = false;
self.update_footer_select();
}
pub fn page_up(&mut self) {
self.curr.selected = self.curr.selected.saturating_sub(self.last_height);
self.curr.first_item = self.curr.first_item.min(self.curr.selected);
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();
}
@ -125,19 +253,21 @@ impl LogViewer {
if self.footer_selected {
self.footer_list.select(Some(0));
} else {
self.curr.selected = 0;
self.curr.first_item = 0;
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.selected).collect()
self.stack.iter().map(|i| i.selection_offset).collect()
}
pub fn back(&mut self) {
self.cache.insert(self.path(), self.curr);
self.curr = self.stack.pop().unwrap_or_default();
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();
}
@ -146,19 +276,24 @@ impl LogViewer {
self.footer_selected = !self.footer_selected;
}
pub fn enter(&mut self, file: &mut LogfileReader) {
pub fn enter(&mut self) {
if !self.footer_selected {
let Some(s) = self.selected(file) else {
let Some(s) = self.selected() else {
return;
};
if let LogEntry::Single { .. } = s.as_ref() {
let Some(i) = s.from_start() else {
return;
}
};
self.stack
.push(mem::replace(&mut self.curr, LogView::default()));
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;
self.curr = cached_view.clone();
}
self.update_footer_select();
}