fix some iteration bugs
This commit is contained in:
parent
de51666742
commit
8eab2502c7
8 changed files with 143 additions and 89 deletions
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -182,8 +182,10 @@ impl App {
|
||||||
|
|
||||||
for file in fs::read_dir(logs_dir)? {
|
for file in fs::read_dir(logs_dir)? {
|
||||||
let file = file?;
|
let file = file?;
|
||||||
|
if file.path().extension().is_some_and(|ext| ext == "log") {
|
||||||
files.push(file);
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)>>>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue