From de5166674281bbcc8f07be2903b1af4051cff4a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Wed, 25 Feb 2026 17:14:40 +0100 Subject: [PATCH] forward filters (ugly) --- src/tui/filter.rs | 8 +- src/tui/log_viewer/filters.rs | 2 +- src/tui/log_viewer/mod.rs | 155 ++++++++++++++++++++++------------ src/tui/log_viewer/view.rs | 11 ++- src/tui/model.rs | 24 +++--- src/tui/processing.rs | 46 ++++++---- src/tui/reader.rs | 6 +- src/tui/widgets/items.rs | 12 ++- 8 files changed, 167 insertions(+), 97 deletions(-) diff --git a/src/tui/filter.rs b/src/tui/filter.rs index e345ea8..302030d 100644 --- a/src/tui/filter.rs +++ b/src/tui/filter.rs @@ -25,7 +25,7 @@ mod serialize_regex { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub enum MatcherValue { Exact(String), Regex(#[serde(with = "serialize_regex")] Regex), @@ -53,7 +53,7 @@ impl MatcherValue { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub enum Matcher { Field { name: String, value: MatcherValue }, Message { value: MatcherValue }, @@ -98,13 +98,13 @@ impl Matcher { } } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub enum FilterKind { Inline, Remove, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct Filter { pub matcher: Matcher, pub kind: FilterKind, diff --git a/src/tui/log_viewer/filters.rs b/src/tui/log_viewer/filters.rs index 789228d..3d5ee53 100644 --- a/src/tui/log_viewer/filters.rs +++ b/src/tui/log_viewer/filters.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::tui::filter::Filter; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Filters { filters: Vec>, undo_pos: usize, diff --git a/src/tui/log_viewer/mod.rs b/src/tui/log_viewer/mod.rs index dbfb058..d0a5e25 100644 --- a/src/tui/log_viewer/mod.rs +++ b/src/tui/log_viewer/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, iter, mem, sync::Arc}; +use std::{collections::HashMap, fs::File, io::Write, iter, mem, sync::Arc}; use crate::tui::{ filter::Filter, @@ -8,7 +8,7 @@ use crate::tui::{ view::LogView, }, model::LogEntry, - processing::{IntoLogStream, LogStream}, + processing::{FilterList, IntoLogStream, LogStream}, }; use tui_widget_list::ListState; @@ -56,82 +56,95 @@ impl LogViewer { } pub fn filtered_root_stream(&self) -> Box { - let mut curr = self.root_stream.clone(); - for filter in self.filters.get() { - curr = Box::new(curr.filter(Arc::clone(filter))); - } - - curr + self.root_stream.with_filters(self.filters.get()) } - fn update_filters(&mut self) { + fn update_filters(&mut self, old_filters: &FilterList) { self.cache.clear(); let offsets_list: Vec<_> = self .stack .iter() - .map(|i| (i.selected(), i.selection_offset)) + .map(|i| (i.selected(old_filters), i.selection_offset)) .chain(iter::once(( - self.curr.selected(), + self.curr.selected(old_filters), self.curr.selection_offset, ))) .collect(); - fn find_elem_in_stream( - stream: &dyn LogStream, - elem: &Arc, - ) -> Option> { - let mut temp_stream = stream.clone(); - let mut max = 100usize; - while let Some((curr, _)) = temp_stream.next() { - if Arc::ptr_eq(&curr, elem) { - return Some(temp_stream); + 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::>() + ) + .unwrap(); + + let find_elem_in_stream = + |stream: &dyn LogStream, elem: &Arc| -> Option> { + 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); + } } - if max == 0 { - break; - } - max -= 1; - } - - None - } + None + }; let mut current_stream = self.filtered_root_stream(); let mut new_stack = Vec::::new(); + let mut missing = false; - 'outer: for (elem, old_offset) in offsets_list { - let Some((elem, _)) = elem else { + for (elem, old_offset) in offsets_list { + let Some((elem, inline_depth)) = elem else { + writeln!(&mut log, "no elem").unwrap(); 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; - // } + 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; } - break 'outer; + 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().is_none() { + if stream.prev(self.filters.get()).is_none() { break; } offset += 1; @@ -142,29 +155,60 @@ impl LogViewer { 0 }; - current_stream = stream.clone(); + 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::>() + ) + .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: 0, + selection_offset: self.stack.first().unwrap_or(&self.curr).selection_offset, }; } self.stack = new_stack; } pub fn add_filter(&mut self, filter: Arc) { + let old_filters = self.filters.clone(); self.filters.push(Arc::clone(&filter)); - self.update_filters(); + self.update_filters(old_filters.get()); } pub fn update_num_items(&mut self, num_visible_items: usize) { @@ -172,7 +216,7 @@ impl LogViewer { if self.curr.selection_offset == 0 { break; } - self.curr.iter.next(); + self.curr.iter.next(self.filters.get()); self.curr.selection_offset -= 1; } self.last_height = num_visible_items; @@ -204,7 +248,7 @@ impl LogViewer { let mut temp_iter = self.curr.iter.clone(); let mut res = Vec::new(); for _ in 0..max { - let Some(i) = temp_iter.next() else { + let Some(i) = temp_iter.next(self.filters.get()) else { break; }; res.push(i); @@ -242,7 +286,7 @@ impl LogViewer { } pub fn selected(&self) -> Option<(Arc, usize)> { - self.curr.selected() + self.curr.selected(self.filters.get()) } pub fn prev(&mut self) { @@ -253,7 +297,7 @@ impl LogViewer { } _ => { if self.curr.selection_offset == 0 { - let _ = self.curr.iter.prev(); + let _ = self.curr.iter.prev(self.filters.get()); } else { self.curr.selection_offset -= 1; } @@ -283,7 +327,7 @@ impl LogViewer { pub fn page_up(&mut self) { for _ in 0..self.last_height { if self.curr.selection_offset == 0 { - let _ = self.curr.iter.prev(); + let _ = self.curr.iter.prev(self.filters.get()); } else { self.curr.selection_offset -= 1; } @@ -299,7 +343,7 @@ impl LogViewer { } _ => { self.curr.selection_offset = 0; - while self.curr.iter.prev().is_some() {} + while self.curr.iter.prev(self.filters.get()).is_some() {} self.input_state = InputState::None; } } @@ -318,13 +362,15 @@ impl LogViewer { } pub fn undo(&mut self) { + let old_filters = self.filters.clone(); self.filters.undo(); - self.update_filters(); + self.update_filters(old_filters.get()); } pub fn redo(&mut self) { + let old_filters = self.filters.clone(); self.filters.redo(); - self.update_filters(); + self.update_filters(old_filters.get()); } pub fn enter(&mut self) { @@ -333,21 +379,24 @@ impl LogViewer { let Some((s, _)) = self.selected() else { return; }; - let Some(i) = s.from_start(0) else { + let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else { return; }; - if i.clone().next().is_none() { + if i.clone().next(self.filters.get()).is_none() { return; } - if i.clone().next().is_some_and(|(i, _)| i.is_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: Box::new(i), + iter: i, selection_offset: 0, }, )); diff --git a/src/tui/log_viewer/view.rs b/src/tui/log_viewer/view.rs index 4e96f77..deda644 100644 --- a/src/tui/log_viewer/view.rs +++ b/src/tui/log_viewer/view.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use crate::tui::{model::LogEntry, processing::LogStream}; +use crate::tui::{ + model::LogEntry, + processing::{FilterList, LogStream}, +}; pub struct LogView { pub iter: Box, @@ -8,13 +11,13 @@ pub struct LogView { } impl LogView { - pub fn selected(&self) -> Option<(Arc, usize)> { + pub fn selected(&self, fl: &FilterList) -> Option<(Arc, usize)> { let mut temp_iter = self.iter.clone(); for _ in 0..self.selection_offset { - let _ = temp_iter.next()?; + let _ = temp_iter.next(fl)?; } - temp_iter.next() + temp_iter.next(fl) } } diff --git a/src/tui/model.rs b/src/tui/model.rs index 3aab5cf..c4ad8f9 100644 --- a/src/tui/model.rs +++ b/src/tui/model.rs @@ -149,22 +149,22 @@ impl LogEntry { enter, sub_entries, .. } => { if let Some(val) = enter.all_fields().fields.get("name") { - LineText::new( - if sub_entries.is_empty() - || sub_entries.get(0).is_some_and(|i| i.is_return()) - { - SPACES_BEFORE.to_string() - } else { + let (prefix, sym) = if sub_entries.is_empty() + || sub_entries.get(0).is_some_and(|i| i.is_return()) + { + (SPACES_BEFORE.to_string(), "⟲") + } else { + ( format!( "{:4}⭣{:4}⇊ ", sub_entries.len(), self.count().wrapping_sub(1) - ) - }, - format!("↪ {val}"), - self.message_or_name(), - tree, - ) + ), + "↪", + ) + }; + + LineText::new(prefix, format!("{sym} {val}"), self.message_or_name(), tree) } else { LineText::new( SPACES_BEFORE.to_string(), diff --git a/src/tui/processing.rs b/src/tui/processing.rs index 793dd38..805780d 100644 --- a/src/tui/processing.rs +++ b/src/tui/processing.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +pub type FilterList = [Arc]; + use crate::tui::{ filter::{Filter, FilterKind}, model::LogEntry, @@ -19,7 +21,7 @@ pub struct LogEntryStream { } impl LogStream for LogEntryStream { - fn next(&mut self) -> Option<(Arc, usize)> { + fn next(&mut self, _fl: &FilterList) -> Option<(Arc, usize)> { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { return None; }; @@ -29,7 +31,7 @@ impl LogStream for LogEntryStream { Some((Arc::clone(res), self.inline_depth)) } - fn prev(&mut self) -> Option<(Arc, usize)> { + fn prev(&mut self, _fl: &FilterList) -> Option<(Arc, usize)> { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { return None; }; @@ -85,8 +87,8 @@ impl IntoLogStream for &Arc { } pub trait LogStream { - fn next(&mut self) -> Option<(Arc, usize)>; - fn prev(&mut self) -> Option<(Arc, usize)>; + fn next(&mut self, fl: &FilterList) -> Option<(Arc, usize)>; + fn prev(&mut self, fl: &FilterList) -> Option<(Arc, usize)>; fn enclosing_log_entry(&self) -> Option<(Arc, usize)>; @@ -98,6 +100,15 @@ pub trait LogStream { stack: vec![(self.enclosing_log_entry(), self.clone())], } } + + fn with_filters(&self, fl: &FilterList) -> Box { + let mut curr = self.clone(); + for filter in fl { + curr = Box::new(curr.filter(Arc::clone(filter))); + } + + curr + } } pub struct FilteredLogStream { @@ -106,11 +117,11 @@ pub struct FilteredLogStream { } macro_rules! generate_candidate { - ($_self: tt, $iter_method: ident, $forwards: expr) => { + ($_self: tt, $iter_method: ident, $fl: ident, $forwards: expr) => { loop { let stack_len = $_self.stack.len(); let (_enclosing, top) = $_self.stack.last_mut().unwrap(); - if let Some((top, inline_depth)) = top.$iter_method() { + if let Some((top, inline_depth)) = top.$iter_method($fl) { // if we can find it in the top of stack iterator, neat! return Some((top, inline_depth)); } else if stack_len > 1 { @@ -126,9 +137,9 @@ macro_rules! generate_candidate { } macro_rules! generate_filter { - ($_self: tt, $candidate: ident, $into_iter: ident, $forwards: expr) => { + ($_self: tt, $candidate: ident, $fl: ident, $into_iter: ident, $forwards: expr) => { loop { - let (elem, inline_depth) = $_self.$candidate()?; + let (elem, inline_depth) = $_self.$candidate($fl)?; let Filter { matcher, kind } = $_self.filter.as_ref(); @@ -138,9 +149,10 @@ macro_rules! generate_filter { // When we inline, add this item to the stack // so we continue iterating inside it. if let Some(iter) = elem.$into_iter(inline_depth + 1) { + let iter = iter.with_filters($fl); $_self .stack - .push((Some((Arc::clone(&elem), inline_depth)), Box::new(iter))); + .push((Some((Arc::clone(&elem), inline_depth)), iter)); } // Continue so we actually return a nested item. if $forwards { @@ -162,21 +174,21 @@ macro_rules! generate_filter { } impl FilteredLogStream { - fn next_candidate(&mut self) -> Option<(Arc, usize)> { - generate_candidate!(self, next, true) + fn next_candidate(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { + generate_candidate!(self, next, fl, true) } - fn prev_candidate(&mut self) -> Option<(Arc, usize)> { - generate_candidate!(self, prev, false) + fn prev_candidate(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { + generate_candidate!(self, prev, fl, false) } } impl LogStream for FilteredLogStream { - fn next(&mut self) -> Option<(Arc, usize)> { - generate_filter!(self, next_candidate, from_start, true) + fn next(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { + generate_filter!(self, next_candidate, fl, from_start, true) } - fn prev(&mut self) -> Option<(Arc, usize)> { - generate_filter!(self, prev_candidate, from_end, false) + fn prev(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { + generate_filter!(self, prev_candidate, fl, from_end, false) } fn clone(&self) -> Box { diff --git a/src/tui/reader.rs b/src/tui/reader.rs index a773e8e..b6d52ad 100644 --- a/src/tui/reader.rs +++ b/src/tui/reader.rs @@ -14,7 +14,7 @@ use std::{ use crate::tui::{ model::{LogEntry, RawLogEntry}, - processing::LogStream, + processing::{FilterList, LogStream}, }; struct LogFileEntryGenerator { @@ -139,14 +139,14 @@ pub struct LogFileReaderStream { } impl LogStream for LogFileReaderStream { - fn next(&mut self) -> Option<(Arc, usize)> { + fn next(&mut self, _fl: &FilterList) -> Option<(Arc, usize)> { let entry = self.reader.fill_buf_to_access_index(self.curr)?; self.curr += 1; Some((entry, 0)) } - fn prev(&mut self) -> Option<(Arc, usize)> { + fn prev(&mut self, _fl: &FilterList) -> Option<(Arc, usize)> { if self.curr == 0 { return None; } diff --git a/src/tui/widgets/items.rs b/src/tui/widgets/items.rs index b2513a1..8bfb0d2 100644 --- a/src/tui/widgets/items.rs +++ b/src/tui/widgets/items.rs @@ -1,4 +1,4 @@ -use std::{cell::OnceCell, sync::Arc}; +use std::{cell::OnceCell, iter, sync::Arc}; use itertools::Itertools; use ratatui::widgets::{List, ListItem, Widget}; @@ -24,7 +24,12 @@ impl TreeState { let mut curr = String::new(); let mut prev_depth = 0; - for (depth, next_depth) in items.iter().map(|i| i.1).circular_tuple_windows() { + for (depth, next_depth) in items + .iter() + .map(|i| i.1 as isize) + .chain(iter::once(-1isize)) + .tuple_windows() + { if depth > prev_depth { if depth > 1 { let _ = curr.pop(); @@ -43,6 +48,7 @@ impl TreeState { curr.push('└'); curr.push('─'); } else { + curr.push(' '); curr.push(' '); curr.push(' '); curr.push('├'); @@ -65,7 +71,7 @@ impl TreeState { if depth > 0 { let _ = curr.pop(); let _ = curr.pop(); - if next_depth < depth - 1 { + if next_depth < depth { curr.push('└'); curr.push('─'); } else {