forward filters (ugly)

This commit is contained in:
Jana Dönszelmann 2026-02-25 17:14:40 +01:00
parent 1f6679f57f
commit de51666742
No known key found for this signature in database
8 changed files with 167 additions and 97 deletions

View file

@ -25,7 +25,7 @@ mod serialize_regex {
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub enum MatcherValue { pub enum MatcherValue {
Exact(String), Exact(String),
Regex(#[serde(with = "serialize_regex")] Regex), Regex(#[serde(with = "serialize_regex")] Regex),
@ -53,7 +53,7 @@ impl MatcherValue {
} }
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub enum Matcher { pub enum Matcher {
Field { name: String, value: MatcherValue }, Field { name: String, value: MatcherValue },
Message { value: MatcherValue }, Message { value: MatcherValue },
@ -98,13 +98,13 @@ impl Matcher {
} }
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize, Debug)]
pub enum FilterKind { pub enum FilterKind {
Inline, Inline,
Remove, Remove,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub struct Filter { pub struct Filter {
pub matcher: Matcher, pub matcher: Matcher,
pub kind: FilterKind, pub kind: FilterKind,

View file

@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use crate::tui::filter::Filter; use crate::tui::filter::Filter;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Filters { pub struct Filters {
filters: Vec<Arc<Filter>>, filters: Vec<Arc<Filter>>,
undo_pos: usize, undo_pos: usize,

View file

@ -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::{ use crate::tui::{
filter::Filter, filter::Filter,
@ -8,7 +8,7 @@ use crate::tui::{
view::LogView, view::LogView,
}, },
model::LogEntry, model::LogEntry,
processing::{IntoLogStream, LogStream}, processing::{FilterList, IntoLogStream, LogStream},
}; };
use tui_widget_list::ListState; use tui_widget_list::ListState;
@ -56,82 +56,95 @@ impl LogViewer {
} }
pub fn filtered_root_stream(&self) -> Box<dyn LogStream> { pub fn filtered_root_stream(&self) -> Box<dyn LogStream> {
let mut curr = self.root_stream.clone(); self.root_stream.with_filters(self.filters.get())
for filter in self.filters.get() {
curr = Box::new(curr.filter(Arc::clone(filter)));
} }
curr fn update_filters(&mut self, old_filters: &FilterList) {
}
fn update_filters(&mut self) {
self.cache.clear(); self.cache.clear();
let offsets_list: Vec<_> = self let offsets_list: Vec<_> = self
.stack .stack
.iter() .iter()
.map(|i| (i.selected(), i.selection_offset)) .map(|i| (i.selected(old_filters), i.selection_offset))
.chain(iter::once(( .chain(iter::once((
self.curr.selected(), self.curr.selected(old_filters),
self.curr.selection_offset, self.curr.selection_offset,
))) )))
.collect(); .collect();
fn find_elem_in_stream( let mut log = File::options()
stream: &dyn LogStream, .create(true)
elem: &Arc<LogEntry>, .append(true)
) -> Option<Box<dyn LogStream>> { .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(); let mut temp_stream = stream.clone();
let mut max = 100usize; while let Some((curr, _)) = temp_stream.next(self.filters.get()) {
while let Some((curr, _)) = temp_stream.next() {
if Arc::ptr_eq(&curr, elem) { if Arc::ptr_eq(&curr, elem) {
return Some(temp_stream); return Some(temp_stream);
} }
if max == 0 {
break;
}
max -= 1;
} }
None None
} };
let mut current_stream = self.filtered_root_stream(); let mut current_stream = self.filtered_root_stream();
let mut new_stack = Vec::<LogView>::new(); let mut new_stack = Vec::<LogView>::new();
let mut missing = false;
'outer: for (elem, old_offset) in offsets_list { for (elem, old_offset) in offsets_list {
let Some((elem, _)) = elem else { let Some((elem, inline_depth)) = elem else {
writeln!(&mut log, "no elem").unwrap();
break; break;
}; };
writeln!(
// If the value we're looking for is removed by the filter, &mut log,
// we'll have a hard time finding it so quit "reconstruction {:?} prev at {old_offset:?}",
// if filter.removes(&elem) { elem.message_or_name()
// break; )
// } .unwrap();
// find the nearest stream in which this element can be found // find the nearest stream in which this element can be found
let mut curr = current_stream.as_ref(); let mut curr = current_stream.as_ref();
let mut parents = new_stack.iter().rev(); let mut parents = new_stack.iter().rev();
let mut found_in_toplevel = true; let mut found_in_toplevel = true;
let mut stream = loop { let mut stream = loop {
writeln!(&mut log, "find in stream").unwrap();
if let Some(stream) = find_elem_in_stream(curr, &elem) { if let Some(stream) = find_elem_in_stream(curr, &elem) {
writeln!(&mut log, "found (toplevel={found_in_toplevel})").unwrap();
break stream; break stream;
} }
found_in_toplevel = false; found_in_toplevel = false;
if let Some(parent) = parents.next() { if let Some(parent) = parents.next() {
writeln!(&mut log, "searching parent").unwrap();
curr = parent.iter.as_ref(); curr = parent.iter.as_ref();
continue; 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 offset = if found_in_toplevel {
let mut offset = 0; let mut offset = 0;
for _ in 0..old_offset { for _ in 0..old_offset {
if stream.prev().is_none() { if stream.prev(self.filters.get()).is_none() {
break; break;
} }
offset += 1; offset += 1;
@ -142,29 +155,60 @@ impl LogViewer {
0 0
}; };
current_stream = stream.clone(); writeln!(&mut log, "new offset: {offset:?}").unwrap();
let nested_stream = elem.from_start(inline_depth);
new_stack.push(LogView { new_stack.push(LogView {
iter: stream, iter: stream,
selection_offset: offset, 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 // Take the top of the stack as `curr`, unless
// the new stack is empty, then just reset the current view. // the new stack is empty, then just reset the current view.
if let Some(curr) = new_stack.pop() { if let Some(curr) = new_stack.pop() {
writeln!(&mut log, "popped new curr off stack").unwrap();
self.curr = curr; self.curr = curr;
} else { } else {
writeln!(&mut log, "reconstructing root, somehow").unwrap();
self.curr = LogView { self.curr = LogView {
iter: self.filtered_root_stream().clone(), iter: self.filtered_root_stream().clone(),
selection_offset: 0, selection_offset: self.stack.first().unwrap_or(&self.curr).selection_offset,
}; };
} }
self.stack = new_stack; self.stack = new_stack;
} }
pub fn add_filter(&mut self, filter: Arc<Filter>) { pub fn add_filter(&mut self, filter: Arc<Filter>) {
let old_filters = self.filters.clone();
self.filters.push(Arc::clone(&filter)); 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) { pub fn update_num_items(&mut self, num_visible_items: usize) {
@ -172,7 +216,7 @@ impl LogViewer {
if self.curr.selection_offset == 0 { if self.curr.selection_offset == 0 {
break; break;
} }
self.curr.iter.next(); self.curr.iter.next(self.filters.get());
self.curr.selection_offset -= 1; self.curr.selection_offset -= 1;
} }
self.last_height = num_visible_items; self.last_height = num_visible_items;
@ -204,7 +248,7 @@ impl LogViewer {
let mut temp_iter = self.curr.iter.clone(); let mut temp_iter = self.curr.iter.clone();
let mut res = Vec::new(); let mut res = Vec::new();
for _ in 0..max { for _ in 0..max {
let Some(i) = temp_iter.next() else { let Some(i) = temp_iter.next(self.filters.get()) else {
break; break;
}; };
res.push(i); res.push(i);
@ -242,7 +286,7 @@ impl LogViewer {
} }
pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> { pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> {
self.curr.selected() self.curr.selected(self.filters.get())
} }
pub fn prev(&mut self) { pub fn prev(&mut self) {
@ -253,7 +297,7 @@ impl LogViewer {
} }
_ => { _ => {
if self.curr.selection_offset == 0 { if self.curr.selection_offset == 0 {
let _ = self.curr.iter.prev(); let _ = self.curr.iter.prev(self.filters.get());
} else { } else {
self.curr.selection_offset -= 1; self.curr.selection_offset -= 1;
} }
@ -283,7 +327,7 @@ impl LogViewer {
pub fn page_up(&mut self) { pub fn page_up(&mut self) {
for _ in 0..self.last_height { for _ in 0..self.last_height {
if self.curr.selection_offset == 0 { if self.curr.selection_offset == 0 {
let _ = self.curr.iter.prev(); let _ = self.curr.iter.prev(self.filters.get());
} else { } else {
self.curr.selection_offset -= 1; self.curr.selection_offset -= 1;
} }
@ -299,7 +343,7 @@ impl LogViewer {
} }
_ => { _ => {
self.curr.selection_offset = 0; 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; self.input_state = InputState::None;
} }
} }
@ -318,13 +362,15 @@ impl LogViewer {
} }
pub fn undo(&mut self) { pub fn undo(&mut self) {
let old_filters = self.filters.clone();
self.filters.undo(); self.filters.undo();
self.update_filters(); self.update_filters(old_filters.get());
} }
pub fn redo(&mut self) { pub fn redo(&mut self) {
let old_filters = self.filters.clone();
self.filters.redo(); self.filters.redo();
self.update_filters(); self.update_filters(old_filters.get());
} }
pub fn enter(&mut self) { pub fn enter(&mut self) {
@ -333,21 +379,24 @@ impl LogViewer {
let Some((s, _)) = self.selected() else { let Some((s, _)) = self.selected() else {
return; 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; return;
}; };
if i.clone().next().is_none() { if i.clone().next(self.filters.get()).is_none() {
return; 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; return;
} }
self.stack.push(mem::replace( self.stack.push(mem::replace(
&mut self.curr, &mut self.curr,
LogView { LogView {
iter: Box::new(i), iter: i,
selection_offset: 0, selection_offset: 0,
}, },
)); ));

View file

@ -1,6 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use crate::tui::{model::LogEntry, processing::LogStream}; use crate::tui::{
model::LogEntry,
processing::{FilterList, LogStream},
};
pub struct LogView { pub struct LogView {
pub iter: Box<dyn LogStream>, pub iter: Box<dyn LogStream>,
@ -8,13 +11,13 @@ pub struct LogView {
} }
impl LogView { impl LogView {
pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> { pub fn selected(&self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
let mut temp_iter = self.iter.clone(); let mut temp_iter = self.iter.clone();
for _ in 0..self.selection_offset { for _ in 0..self.selection_offset {
let _ = temp_iter.next()?; let _ = temp_iter.next(fl)?;
} }
temp_iter.next() temp_iter.next(fl)
} }
} }

View file

@ -149,22 +149,22 @@ impl LogEntry {
enter, sub_entries, .. enter, sub_entries, ..
} => { } => {
if let Some(val) = enter.all_fields().fields.get("name") { if let Some(val) = enter.all_fields().fields.get("name") {
LineText::new( let (prefix, sym) = if sub_entries.is_empty()
if sub_entries.is_empty()
|| sub_entries.get(0).is_some_and(|i| i.is_return()) || sub_entries.get(0).is_some_and(|i| i.is_return())
{ {
SPACES_BEFORE.to_string() (SPACES_BEFORE.to_string(), "")
} else { } else {
(
format!( format!(
"{:4}⭣{:4}⇊ ", "{:4}⭣{:4}⇊ ",
sub_entries.len(), sub_entries.len(),
self.count().wrapping_sub(1) self.count().wrapping_sub(1)
),
"",
) )
}, };
format!("{val}"),
self.message_or_name(), LineText::new(prefix, format!("{sym} {val}"), self.message_or_name(), tree)
tree,
)
} else { } else {
LineText::new( LineText::new(
SPACES_BEFORE.to_string(), SPACES_BEFORE.to_string(),

View file

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
pub type FilterList = [Arc<Filter>];
use crate::tui::{ use crate::tui::{
filter::{Filter, FilterKind}, filter::{Filter, FilterKind},
model::LogEntry, model::LogEntry,
@ -19,7 +21,7 @@ pub struct LogEntryStream {
} }
impl LogStream for LogEntryStream { impl LogStream for LogEntryStream {
fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn next(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
return None; return None;
}; };
@ -29,7 +31,7 @@ impl LogStream for LogEntryStream {
Some((Arc::clone(res), self.inline_depth)) Some((Arc::clone(res), self.inline_depth))
} }
fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn prev(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
return None; return None;
}; };
@ -85,8 +87,8 @@ impl IntoLogStream for &Arc<LogEntry> {
} }
pub trait LogStream { pub trait LogStream {
fn next(&mut self) -> Option<(Arc<LogEntry>, usize)>; fn next(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)>;
fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)>; fn prev(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)>;
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)>; fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)>;
@ -98,6 +100,15 @@ pub trait LogStream {
stack: vec![(self.enclosing_log_entry(), self.clone())], stack: vec![(self.enclosing_log_entry(), self.clone())],
} }
} }
fn with_filters(&self, fl: &FilterList) -> Box<dyn LogStream> {
let mut curr = self.clone();
for filter in fl {
curr = Box::new(curr.filter(Arc::clone(filter)));
}
curr
}
} }
pub struct FilteredLogStream { pub struct FilteredLogStream {
@ -106,11 +117,11 @@ pub struct FilteredLogStream {
} }
macro_rules! generate_candidate { macro_rules! generate_candidate {
($_self: tt, $iter_method: ident, $forwards: expr) => { ($_self: tt, $iter_method: ident, $fl: ident, $forwards: expr) => {
loop { loop {
let stack_len = $_self.stack.len(); let stack_len = $_self.stack.len();
let (_enclosing, top) = $_self.stack.last_mut().unwrap(); 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! // if we can find it in the top of stack iterator, neat!
return Some((top, inline_depth)); return Some((top, inline_depth));
} else if stack_len > 1 { } else if stack_len > 1 {
@ -126,9 +137,9 @@ macro_rules! generate_candidate {
} }
macro_rules! generate_filter { 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 { loop {
let (elem, inline_depth) = $_self.$candidate()?; let (elem, inline_depth) = $_self.$candidate($fl)?;
let Filter { matcher, kind } = $_self.filter.as_ref(); 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 // When we inline, add this item to the stack
// so we continue iterating inside it. // so we continue iterating inside it.
if let Some(iter) = elem.$into_iter(inline_depth + 1) { if let Some(iter) = elem.$into_iter(inline_depth + 1) {
let iter = iter.with_filters($fl);
$_self $_self
.stack .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. // Continue so we actually return a nested item.
if $forwards { if $forwards {
@ -162,21 +174,21 @@ macro_rules! generate_filter {
} }
impl FilteredLogStream { impl FilteredLogStream {
fn next_candidate(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn next_candidate(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
generate_candidate!(self, next, true) generate_candidate!(self, next, fl, true)
} }
fn prev_candidate(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn prev_candidate(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
generate_candidate!(self, prev, false) generate_candidate!(self, prev, fl, false)
} }
} }
impl LogStream for FilteredLogStream { impl LogStream for FilteredLogStream {
fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn next(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
generate_filter!(self, next_candidate, from_start, true) generate_filter!(self, next_candidate, fl, from_start, true)
} }
fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn prev(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
generate_filter!(self, prev_candidate, from_end, false) generate_filter!(self, prev_candidate, fl, from_end, false)
} }
fn clone(&self) -> Box<dyn LogStream> { fn clone(&self) -> Box<dyn LogStream> {

View file

@ -14,7 +14,7 @@ use std::{
use crate::tui::{ use crate::tui::{
model::{LogEntry, RawLogEntry}, model::{LogEntry, RawLogEntry},
processing::LogStream, processing::{FilterList, LogStream},
}; };
struct LogFileEntryGenerator { struct LogFileEntryGenerator {
@ -139,14 +139,14 @@ pub struct LogFileReaderStream {
} }
impl LogStream for LogFileReaderStream { impl LogStream for LogFileReaderStream {
fn next(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn next(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
let entry = self.reader.fill_buf_to_access_index(self.curr)?; let entry = self.reader.fill_buf_to_access_index(self.curr)?;
self.curr += 1; self.curr += 1;
Some((entry, 0)) Some((entry, 0))
} }
fn prev(&mut self) -> Option<(Arc<LogEntry>, usize)> { fn prev(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
if self.curr == 0 { if self.curr == 0 {
return None; return None;
} }

View file

@ -1,4 +1,4 @@
use std::{cell::OnceCell, sync::Arc}; use std::{cell::OnceCell, iter, sync::Arc};
use itertools::Itertools; use itertools::Itertools;
use ratatui::widgets::{List, ListItem, Widget}; use ratatui::widgets::{List, ListItem, Widget};
@ -24,7 +24,12 @@ impl TreeState {
let mut curr = String::new(); let mut curr = String::new();
let mut prev_depth = 0; 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 > prev_depth {
if depth > 1 { if depth > 1 {
let _ = curr.pop(); let _ = curr.pop();
@ -43,6 +48,7 @@ impl TreeState {
curr.push('└'); curr.push('└');
curr.push('─'); curr.push('─');
} else { } else {
curr.push(' ');
curr.push(' '); curr.push(' ');
curr.push(' '); curr.push(' ');
curr.push('├'); curr.push('├');
@ -65,7 +71,7 @@ impl TreeState {
if depth > 0 { if depth > 0 {
let _ = curr.pop(); let _ = curr.pop();
let _ = curr.pop(); let _ = curr.pop();
if next_depth < depth - 1 { if next_depth < depth {
curr.push('└'); curr.push('└');
curr.push('─'); curr.push('─');
} else { } else {