fix some iteration bugs

This commit is contained in:
Jana Dönszelmann 2026-02-25 17:51:31 +01:00
parent de51666742
commit 8eab2502c7
No known key found for this signature in database
8 changed files with 143 additions and 89 deletions

View file

@ -109,12 +109,3 @@ pub struct Filter {
pub matcher: Matcher, pub matcher: Matcher,
pub kind: FilterKind, pub kind: FilterKind,
} }
impl Filter {
pub fn removes(&self, elem: &LogEntry) -> bool {
match self.kind {
FilterKind::Inline => false,
FilterKind::Remove => self.matcher.matches(elem),
}
}
}

View file

@ -1,20 +1,80 @@
use std::sync::Arc; use std::{fs::File, path::PathBuf, sync::Arc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::tui::filter::Filter; use crate::tui::{filter::Filter, widgets::last_error::LastError};
#[derive(Serialize, Deserialize, Debug, Clone)] #[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,
#[serde(skip)]
path: PathBuf,
#[serde(skip)]
error: Option<LastError>,
} }
impl Filters { impl Filters {
pub fn new() -> Self { pub fn new(path: PathBuf, error: LastError) -> Self {
if path.exists() {
match File::open(&path) {
Ok(f) => match serde_json::from_reader(f) {
Ok(i) => {
return Self {
error: Some(error),
path,
..i
};
}
Err(e) => {
error.set(format!(
"failed to read contents of filter file at {}: {e}",
path.display()
));
}
},
Err(e) => {
error.set(format!(
"failed to open filter file at {}: {e}",
path.display()
));
}
}
}
Self { Self {
filters: Vec::new(), filters: Vec::new(),
undo_pos: 0, undo_pos: 0,
path,
error: Some(error),
}
}
fn filters_changed(&self) {
match File::options()
.create(true)
.write(true)
.truncate(true)
.open(&self.path)
{
Ok(f) => {
if let Err(e) = serde_json::to_writer(f, self) {
self.error.as_ref().inspect(|i| {
i.set(format!(
"failed to write contents of filter file at {}: {e}",
self.path.display()
));
});
}
}
Err(e) => {
self.error.as_ref().inspect(|i| {
i.set(format!(
"failed to open filter file at {}: {e}",
self.path.display()
));
});
}
} }
} }
@ -26,6 +86,7 @@ impl Filters {
self.filters.truncate(self.undo_pos); self.filters.truncate(self.undo_pos);
self.filters.push(filter); self.filters.push(filter);
self.undo_pos = self.filters.len(); self.undo_pos = self.filters.len();
self.filters_changed();
} }
pub fn redo(&mut self) { pub fn redo(&mut self) {
@ -33,9 +94,11 @@ impl Filters {
if self.undo_pos > self.filters.len() { if self.undo_pos > self.filters.len() {
self.undo_pos = self.filters.len() self.undo_pos = self.filters.len()
} }
self.filters_changed();
} }
pub fn undo(&mut self) { pub fn undo(&mut self) {
self.undo_pos = self.undo_pos.saturating_sub(1); self.undo_pos = self.undo_pos.saturating_sub(1);
self.filters_changed();
} }
} }

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, fs::File, io::Write, iter, mem, sync::Arc}; use std::{collections::HashMap, iter, mem, path::PathBuf, sync::Arc};
use crate::tui::{ use crate::tui::{
filter::Filter, filter::Filter,
@ -9,6 +9,7 @@ use crate::tui::{
}, },
model::LogEntry, model::LogEntry,
processing::{FilterList, IntoLogStream, LogStream}, processing::{FilterList, IntoLogStream, LogStream},
widgets::last_error::LastError,
}; };
use tui_widget_list::ListState; use tui_widget_list::ListState;
@ -34,7 +35,9 @@ pub struct LogViewer {
} }
impl LogViewer { impl LogViewer {
pub fn new(stream: impl LogStream) -> Self { pub fn new(stream: impl LogStream, filters_path: PathBuf, error: LastError) -> Self {
let filters = Filters::new(filters_path, error);
let stream = stream.with_filters(filters.get());
Self { Self {
stack: Vec::new(), stack: Vec::new(),
curr: LogView { curr: LogView {
@ -50,7 +53,7 @@ impl LogViewer {
last_fields_offset: 0, last_fields_offset: 0,
last_fields_height: 0, last_fields_height: 0,
filters: Filters::new(), filters,
input_state: InputState::None, input_state: InputState::None,
} }
} }
@ -71,20 +74,20 @@ impl LogViewer {
))) )))
.collect(); .collect();
let mut log = File::options() // let mut log = File::options()
.create(true) // .create(true)
.append(true) // .append(true)
.open("./log") // .open("./log")
.unwrap(); // .unwrap();
writeln!(&mut log, "active filters: {:?}", self.filters.get()).unwrap(); // writeln!(&mut log, "active filters: {:?}", self.filters.get()).unwrap();
writeln!( // writeln!(
&mut log, // &mut log,
"{:?}", // "{:?}",
offsets_list.iter().map(|i| i.1).collect::<Vec<_>>() // offsets_list.iter().map(|i| i.1).collect::<Vec<_>>()
) // )
.unwrap(); // .unwrap();
let find_elem_in_stream = let find_elem_in_stream =
|stream: &dyn LogStream, elem: &Arc<LogEntry>| -> Option<Box<dyn LogStream>> { |stream: &dyn LogStream, elem: &Arc<LogEntry>| -> Option<Box<dyn LogStream>> {
@ -104,35 +107,35 @@ impl LogViewer {
for (elem, old_offset) in offsets_list { for (elem, old_offset) in offsets_list {
let Some((elem, inline_depth)) = elem else { let Some((elem, inline_depth)) = elem else {
writeln!(&mut log, "no elem").unwrap(); // writeln!(&mut log, "no elem").unwrap();
break; break;
}; };
writeln!( // writeln!(
&mut log, // &mut log,
"reconstruction {:?} prev at {old_offset:?}", // "reconstruction {:?} prev at {old_offset:?}",
elem.message_or_name() // elem.message_or_name()
) // )
.unwrap(); // .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(); // 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(); // 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(); // writeln!(&mut log, "searching parent").unwrap();
curr = parent.iter.as_ref(); curr = parent.iter.as_ref();
continue; continue;
} }
writeln!(&mut log, "no more parents").unwrap(); // writeln!(&mut log, "no more parents").unwrap();
missing = true; missing = true;
// TODO: better guess at how far down we need to go // TODO: better guess at how far down we need to go
@ -155,7 +158,7 @@ impl LogViewer {
0 0
}; };
writeln!(&mut log, "new offset: {offset:?}").unwrap(); // writeln!(&mut log, "new offset: {offset:?}").unwrap();
let nested_stream = elem.from_start(inline_depth); let nested_stream = elem.from_start(inline_depth);
new_stack.push(LogView { new_stack.push(LogView {
@ -174,29 +177,29 @@ impl LogViewer {
} }
} }
writeln!( // writeln!(
&mut log, // &mut log,
"{:?}", // "{:?}",
new_stack // new_stack
.iter() // .iter()
.map(|i| ( // .map(|i| (
i.iter // i.iter
.clone() // .clone()
.next(self.filters.get()) // .next(self.filters.get())
.map(|i| i.0.message_or_name()), // .map(|i| i.0.message_or_name()),
i.selection_offset // i.selection_offset
)) // ))
.collect::<Vec<_>>() // .collect::<Vec<_>>()
) // )
.unwrap(); // .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(); // writeln!(&mut log, "popped new curr off stack").unwrap();
self.curr = curr; self.curr = curr;
} else { } else {
writeln!(&mut log, "reconstructing root, somehow").unwrap(); // 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: self.stack.first().unwrap_or(&self.curr).selection_offset, selection_offset: self.stack.first().unwrap_or(&self.curr).selection_offset,

View file

@ -182,7 +182,9 @@ impl App {
for file in fs::read_dir(logs_dir)? { for file in fs::read_dir(logs_dir)? {
let file = file?; let file = file?;
files.push(file); if file.path().extension().is_some_and(|ext| ext == "log") {
files.push(file);
}
} }
Ok(files) Ok(files)
@ -207,6 +209,23 @@ impl App {
self.tabs.last_mut().unwrap() self.tabs.last_mut().unwrap()
} }
fn start_log_viewer(&mut self, path: PathBuf) {
let filters_path = path.with_added_extension("filters.json");
match LogfileReader::new(&path) {
Ok(i) => {
self.current_file = Some(i.clone());
self.replace_tab(Tab::LogViewer(LogViewer::new(
i.iter(),
filters_path,
self.last_error.clone(),
)));
}
Err(_) => {
panic!()
}
}
}
fn handle_current_tab_keycode(&mut self, key: KeyEvent) { fn handle_current_tab_keycode(&mut self, key: KeyEvent) {
match self.tabs.last_mut().unwrap() { match self.tabs.last_mut().unwrap() {
Tab::Help => {} Tab::Help => {}
@ -227,15 +246,8 @@ impl App {
if let Some(selected) = state.selected() if let Some(selected) = state.selected()
&& let Some(selected) = files.get(selected) && let Some(selected) = files.get(selected)
{ {
match LogfileReader::new(&selected.path()) { let path = selected.path();
Ok(i) => { self.start_log_viewer(path);
self.current_file = Some(i.clone());
self.replace_tab(Tab::LogViewer(LogViewer::new(i.iter())));
}
Err(_) => {
panic!()
}
}
} }
} }
_ => {} _ => {}
@ -452,17 +464,8 @@ impl App {
if state.selected() == Some(row) if state.selected() == Some(row)
&& let Some(selected) = files.get(row) && let Some(selected) = files.get(row)
{ {
match LogfileReader::new(&selected.path()) { let path = selected.path();
Ok(i) => { self.start_log_viewer(path);
self.current_file = Some(i.clone());
self.replace_tab(Tab::LogViewer(LogViewer::new(
i.iter(),
)));
}
Err(_) => {
panic!()
}
}
} else { } else {
state.select(Some(row)); state.select(Some(row));
} }

View file

@ -121,7 +121,7 @@ impl LogEntry {
pub fn line_text(&self, tree: String) -> LineText { pub fn line_text(&self, tree: String) -> LineText {
const NO_MESSAGE: &str = "<no message>"; const NO_MESSAGE: &str = "<no message>";
const SPACES_BEFORE: &str = " "; const SPACES_BEFORE: &str = " ";
let single_field = |raw: &RawLogEntry| { let single_field = |raw: &RawLogEntry| {
raw.fields raw.fields
@ -156,7 +156,7 @@ impl LogEntry {
} else { } else {
( (
format!( format!(
"{:4}⭣{:4}⇊ ", "{:5}⭣{:5}⇊ ",
sub_entries.len(), sub_entries.len(),
self.count().wrapping_sub(1) self.count().wrapping_sub(1)
), ),

View file

@ -156,7 +156,8 @@ macro_rules! generate_filter {
} }
// Continue so we actually return a nested item. // Continue so we actually return a nested item.
if $forwards { if $forwards {
return Some((elem, inline_depth)); // return Some((elem, inline_depth));
continue;
} else { } else {
continue; continue;
} }

View file

@ -37,20 +37,14 @@ impl TreeState {
curr.push('│'); curr.push('│');
curr.push(' '); curr.push(' ');
} }
for _ in prev_depth..depth.saturating_sub(1) { for _ in prev_depth..depth.saturating_sub(2) {
curr.push(' '); curr.push(' ');
curr.push(' '); curr.push(' ');
} }
if next_depth < depth { if next_depth < depth {
curr.push(' ');
curr.push(' ');
curr.push(' ');
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 +59,6 @@ impl TreeState {
let _ = curr.pop(); let _ = curr.pop();
let _ = curr.pop(); let _ = curr.pop();
let _ = curr.pop(); let _ = curr.pop();
let _ = curr.pop();
} }
if depth > 0 { if depth > 0 {

View file

@ -4,7 +4,7 @@ use ratatui::widgets::{Paragraph, Widget};
use crate::tui::widgets::styled::Styled; use crate::tui::widgets::styled::Styled;
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct LastError { pub struct LastError {
inner: Rc<RefCell<Option<(String, Instant)>>>, inner: Rc<RefCell<Option<(String, Instant)>>>,
} }