linked lists
This commit is contained in:
parent
8eab2502c7
commit
430c62c120
12 changed files with 783 additions and 544 deletions
|
|
@ -122,7 +122,7 @@ fn main() {
|
|||
"rustc_hir_typeck,rustc_infer,rustc_next_trait_solver,rustc_middle,rustc_traits,rustc_trait_selection,rustc_type_ir,rustc_ty_utils".to_string()
|
||||
}
|
||||
Preset::All => "debug".to_string(),
|
||||
Preset::Crates { crates } => format!("{}", crates.join(",")),
|
||||
Preset::Crates { crates } => crates.join(",").to_string(),
|
||||
};
|
||||
|
||||
let (first, rest) = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
use std::{fs::File, path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -7,6 +12,12 @@ use crate::tui::{filter::Filter, widgets::last_error::LastError};
|
|||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct Filters {
|
||||
filters: Vec<Arc<Filter>>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub direct_children_cache: Arc<Mutex<HashMap<usize, usize>>>,
|
||||
#[serde(skip)]
|
||||
pub all_children_cache: Arc<Mutex<HashMap<usize, usize>>>,
|
||||
|
||||
undo_pos: usize,
|
||||
#[serde(skip)]
|
||||
path: PathBuf,
|
||||
|
|
@ -47,10 +58,14 @@ impl Filters {
|
|||
undo_pos: 0,
|
||||
path,
|
||||
error: Some(error),
|
||||
direct_children_cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
all_children_cache: Arc::new(Mutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn filters_changed(&self) {
|
||||
self.direct_children_cache.lock().unwrap().clear();
|
||||
self.all_children_cache.lock().unwrap().clear();
|
||||
match File::options()
|
||||
.create(true)
|
||||
.write(true)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ impl InputTarget {
|
|||
Self::Fields(None) => "logs with a field...".to_string(),
|
||||
Self::Fields(Some(fm)) => format!("logs with the selected field {}", fm.show()),
|
||||
Self::Text(fm) => format!("logs {}", fm.show()),
|
||||
Self::This => format!("this log"),
|
||||
Self::Surround => format!("the log surrounding the current view"),
|
||||
Self::This => "this log".to_string(),
|
||||
Self::Surround => "the log surrounding the current view".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, iter, mem, path::PathBuf, sync::Arc};
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Arc};
|
||||
|
||||
use crate::tui::{
|
||||
filter::Filter,
|
||||
|
|
@ -8,9 +8,10 @@ use crate::tui::{
|
|||
view::LogView,
|
||||
},
|
||||
model::LogEntry,
|
||||
processing::{FilterList, IntoLogStream, LogStream},
|
||||
processing::Cursor,
|
||||
widgets::last_error::LastError,
|
||||
};
|
||||
use dumpster::sync::Gc;
|
||||
use tui_widget_list::ListState;
|
||||
|
||||
pub mod filters;
|
||||
|
|
@ -18,11 +19,9 @@ pub mod input;
|
|||
pub mod view;
|
||||
|
||||
pub struct LogViewer {
|
||||
pub stack: Vec<LogView>,
|
||||
curr: LogView,
|
||||
cache: HashMap<Vec<usize>, LogView>,
|
||||
|
||||
pub root_stream: Box<dyn LogStream>,
|
||||
pub view: LogView,
|
||||
|
||||
pub last_height: usize,
|
||||
pub last_offset: usize,
|
||||
|
|
@ -30,21 +29,18 @@ pub struct LogViewer {
|
|||
pub last_fields_height: usize,
|
||||
|
||||
pub footer_list: ListState,
|
||||
filters: Filters,
|
||||
pub filters: Filters,
|
||||
pub input_state: InputState,
|
||||
}
|
||||
|
||||
impl LogViewer {
|
||||
pub fn new(stream: impl LogStream, filters_path: PathBuf, error: LastError) -> Self {
|
||||
pub fn new(start: Gc<LogEntry>, filters_path: PathBuf, error: LastError) -> Self {
|
||||
let filters = Filters::new(filters_path, error);
|
||||
let stream = stream.with_filters(filters.get());
|
||||
Self {
|
||||
stack: Vec::new(),
|
||||
curr: LogView {
|
||||
iter: stream.clone(),
|
||||
view: LogView {
|
||||
cursor: Cursor::new(start),
|
||||
selection_offset: 0,
|
||||
},
|
||||
root_stream: stream.clone(),
|
||||
cache: HashMap::new(),
|
||||
footer_list: ListState::default(),
|
||||
|
||||
|
|
@ -58,169 +54,20 @@ impl LogViewer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn filtered_root_stream(&self) -> Box<dyn LogStream> {
|
||||
self.root_stream.with_filters(self.filters.get())
|
||||
}
|
||||
|
||||
fn update_filters(&mut self, old_filters: &FilterList) {
|
||||
self.cache.clear();
|
||||
let offsets_list: Vec<_> = self
|
||||
.stack
|
||||
.iter()
|
||||
.map(|i| (i.selected(old_filters), i.selection_offset))
|
||||
.chain(iter::once((
|
||||
self.curr.selected(old_filters),
|
||||
self.curr.selection_offset,
|
||||
)))
|
||||
.collect();
|
||||
|
||||
// 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::<Vec<_>>()
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
let find_elem_in_stream =
|
||||
|stream: &dyn LogStream, elem: &Arc<LogEntry>| -> Option<Box<dyn LogStream>> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
};
|
||||
|
||||
let mut current_stream = self.filtered_root_stream();
|
||||
let mut new_stack = Vec::<LogView>::new();
|
||||
let mut missing = false;
|
||||
|
||||
for (elem, old_offset) in offsets_list {
|
||||
let Some((elem, inline_depth)) = elem else {
|
||||
// writeln!(&mut log, "no elem").unwrap();
|
||||
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;
|
||||
}
|
||||
|
||||
// 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(self.filters.get()).is_none() {
|
||||
break;
|
||||
}
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
offset
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
// 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::<Vec<_>>()
|
||||
// )
|
||||
// .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: self.stack.first().unwrap_or(&self.curr).selection_offset,
|
||||
};
|
||||
}
|
||||
self.stack = new_stack;
|
||||
}
|
||||
|
||||
pub fn add_filter(&mut self, filter: Arc<Filter>) {
|
||||
let old_filters = self.filters.clone();
|
||||
self.filters.push(Arc::clone(&filter));
|
||||
self.update_filters(old_filters.get());
|
||||
if !self.view.cursor.update_with_parents(&self.filters) {
|
||||
panic!("no log entries");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_num_items(&mut self, num_visible_items: usize) {
|
||||
while self.curr.selection_offset >= num_visible_items {
|
||||
if self.curr.selection_offset == 0 {
|
||||
while self.view.selection_offset >= num_visible_items {
|
||||
if self.view.selection_offset == 0 {
|
||||
break;
|
||||
}
|
||||
self.curr.iter.next(self.filters.get());
|
||||
self.curr.selection_offset -= 1;
|
||||
self.view.cursor.next(&self.filters);
|
||||
self.view.selection_offset -= 1;
|
||||
}
|
||||
self.last_height = num_visible_items;
|
||||
}
|
||||
|
|
@ -229,7 +76,7 @@ impl LogViewer {
|
|||
if let Some((selected, _)) = self.selected() {
|
||||
let ret = match selected.as_ref() {
|
||||
LogEntry::Single { .. } => Default::default(),
|
||||
LogEntry::Sub { sub_entries, .. } => sub_entries.last().and_then(|i| {
|
||||
LogEntry::Sub { children, .. } => children.last_child.as_ref().and_then(|i| {
|
||||
i.all_fields()
|
||||
.get_key_value("return")
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
|
|
@ -247,32 +94,33 @@ impl LogViewer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn items(&mut self, max: usize) -> Option<(Vec<(Arc<LogEntry>, usize)>, usize)> {
|
||||
let mut temp_iter = self.curr.iter.clone();
|
||||
pub fn items(&mut self, max: usize) -> Option<(Vec<(Gc<LogEntry>, usize)>, usize)> {
|
||||
let mut temp_iter = self.view.cursor.clone();
|
||||
let mut res = Vec::new();
|
||||
for _ in 0..max {
|
||||
let Some(i) = temp_iter.next(self.filters.get()) else {
|
||||
if !temp_iter.next(&self.filters) {
|
||||
break;
|
||||
};
|
||||
res.push(i);
|
||||
}
|
||||
// TODO: inline depth
|
||||
res.push((temp_iter.curr(), 0));
|
||||
}
|
||||
|
||||
if res.len() > 0 && self.curr.selection_offset > res.len() - 1 {
|
||||
self.curr.selection_offset = res.len() - 1;
|
||||
if !res.is_empty() && self.view.selection_offset > res.len() - 1 {
|
||||
self.view.selection_offset = res.len() - 1;
|
||||
}
|
||||
|
||||
Some((res, self.curr.selection_offset))
|
||||
Some((res, self.view.selection_offset))
|
||||
}
|
||||
|
||||
pub fn click(&mut self, row: u16) {
|
||||
if row as usize >= self.last_offset {
|
||||
let row_in_list = row as usize - self.last_offset;
|
||||
if row_in_list < self.last_height {
|
||||
if self.curr.selection_offset == row_in_list {
|
||||
if self.view.selection_offset == row_in_list {
|
||||
self.input_state = InputState::None;
|
||||
self.enter();
|
||||
} else {
|
||||
self.curr.selection_offset = row_in_list;
|
||||
self.view.selection_offset = row_in_list;
|
||||
self.input_state = InputState::Target(InputTarget::This);
|
||||
}
|
||||
}
|
||||
|
|
@ -288,8 +136,8 @@ impl LogViewer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> {
|
||||
self.curr.selected(self.filters.get())
|
||||
pub fn selected(&self) -> Option<(Gc<LogEntry>, usize)> {
|
||||
self.view.selected(&self.filters)
|
||||
}
|
||||
|
||||
pub fn prev(&mut self) {
|
||||
|
|
@ -299,10 +147,10 @@ impl LogViewer {
|
|||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||
}
|
||||
_ => {
|
||||
if self.curr.selection_offset == 0 {
|
||||
let _ = self.curr.iter.prev(self.filters.get());
|
||||
if self.view.selection_offset == 0 {
|
||||
self.view.cursor.prev(&self.filters);
|
||||
} else {
|
||||
self.curr.selection_offset -= 1;
|
||||
self.view.selection_offset -= 1;
|
||||
}
|
||||
self.input_state = InputState::None;
|
||||
}
|
||||
|
|
@ -316,23 +164,23 @@ impl LogViewer {
|
|||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||
}
|
||||
_ => {
|
||||
self.curr.selection_offset += 1;
|
||||
self.view.selection_offset += 1;
|
||||
self.input_state = InputState::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_down(&mut self) {
|
||||
self.curr.selection_offset += self.last_height;
|
||||
self.view.selection_offset += self.last_height;
|
||||
self.input_state.reset();
|
||||
}
|
||||
|
||||
pub fn page_up(&mut self) {
|
||||
for _ in 0..self.last_height {
|
||||
if self.curr.selection_offset == 0 {
|
||||
let _ = self.curr.iter.prev(self.filters.get());
|
||||
if self.view.selection_offset == 0 {
|
||||
let _ = self.view.cursor.prev(&self.filters);
|
||||
} else {
|
||||
self.curr.selection_offset -= 1;
|
||||
self.view.selection_offset -= 1;
|
||||
}
|
||||
}
|
||||
self.input_state.reset();
|
||||
|
|
@ -345,67 +193,62 @@ impl LogViewer {
|
|||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||
}
|
||||
_ => {
|
||||
self.curr.selection_offset = 0;
|
||||
while self.curr.iter.prev(self.filters.get()).is_some() {}
|
||||
self.view.selection_offset = 0;
|
||||
while self.view.cursor.prev(&self.filters) {}
|
||||
self.input_state = InputState::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Vec<usize> {
|
||||
self.stack.iter().map(|i| i.selection_offset).collect()
|
||||
}
|
||||
|
||||
pub fn back(&mut self) {
|
||||
self.cache.insert(self.path(), self.curr.clone());
|
||||
if let Some(stack) = self.stack.pop() {
|
||||
self.curr = stack;
|
||||
}
|
||||
self.view.cursor.exit(&self.filters);
|
||||
// self.cache.insert(self.path(), self.curr.clone());
|
||||
self.input_state.reset();
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) {
|
||||
let old_filters = self.filters.clone();
|
||||
self.filters.undo();
|
||||
self.update_filters(old_filters.get());
|
||||
}
|
||||
|
||||
pub fn redo(&mut self) {
|
||||
let old_filters = self.filters.clone();
|
||||
self.filters.redo();
|
||||
self.update_filters(old_filters.get());
|
||||
}
|
||||
|
||||
pub fn enter(&mut self) {
|
||||
match self.input_state {
|
||||
InputState::None => {
|
||||
let Some((s, _)) = self.selected() else {
|
||||
return;
|
||||
};
|
||||
let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if i.clone().next(self.filters.get()).is_none() {
|
||||
return;
|
||||
}
|
||||
if i.clone()
|
||||
.next(self.filters.get())
|
||||
.is_some_and(|(i, _)| i.is_return())
|
||||
{
|
||||
return;
|
||||
for _ in 0..(self.view.selection_offset + 1) {
|
||||
self.view.cursor.next(&self.filters);
|
||||
}
|
||||
|
||||
self.stack.push(mem::replace(
|
||||
&mut self.curr,
|
||||
LogView {
|
||||
iter: i,
|
||||
selection_offset: 0,
|
||||
},
|
||||
));
|
||||
if let Some(cached_view) = self.cache.get(&self.path()) {
|
||||
self.curr = cached_view.clone();
|
||||
}
|
||||
self.view.cursor.enter(&self.filters);
|
||||
// let Some((s, _)) = self.selected() else {
|
||||
// return;
|
||||
// };
|
||||
// let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else {
|
||||
// return;
|
||||
// };
|
||||
//
|
||||
// if i.clone().next(self.filters.get()).is_none() {
|
||||
// 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 {
|
||||
// cursor: i,
|
||||
// selection_offset: 0,
|
||||
// },
|
||||
// ));
|
||||
// if let Some(cached_view) = self.cache.get(&self.path()) {
|
||||
// self.curr = cached_view.clone();
|
||||
// }
|
||||
}
|
||||
InputState::Target(InputTarget::Fields(None)) => {
|
||||
self.input_state =
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
use std::sync::Arc;
|
||||
use dumpster::sync::Gc;
|
||||
|
||||
use crate::tui::{
|
||||
model::LogEntry,
|
||||
processing::{FilterList, LogStream},
|
||||
};
|
||||
use crate::tui::{log_viewer::filters::Filters, model::LogEntry, processing::Cursor};
|
||||
|
||||
pub struct LogView {
|
||||
pub iter: Box<dyn LogStream>,
|
||||
pub cursor: Cursor,
|
||||
pub selection_offset: usize,
|
||||
}
|
||||
|
||||
impl LogView {
|
||||
pub fn selected(&self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
let mut temp_iter = self.iter.clone();
|
||||
for _ in 0..self.selection_offset {
|
||||
let _ = temp_iter.next(fl)?;
|
||||
pub fn selected(&self, filters: &Filters) -> Option<(Gc<LogEntry>, usize)> {
|
||||
let mut temp_iter = self.cursor.clone();
|
||||
for _ in 0..(self.selection_offset + 1) {
|
||||
if !temp_iter.next(filters) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
temp_iter.next(fl)
|
||||
// TODO: inline depth
|
||||
Some((temp_iter.curr(), 0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for LogView {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
iter: self.iter.clone(),
|
||||
selection_offset: self.selection_offset.clone(),
|
||||
cursor: self.cursor.clone(),
|
||||
selection_offset: self.selection_offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,11 +214,16 @@ impl App {
|
|||
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(),
|
||||
)));
|
||||
|
||||
if let Some(first) = i.first() {
|
||||
self.replace_tab(Tab::LogViewer(LogViewer::new(
|
||||
first,
|
||||
filters_path,
|
||||
self.last_error.clone(),
|
||||
)));
|
||||
} else {
|
||||
panic!("no log entries");
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
panic!()
|
||||
|
|
@ -319,7 +324,7 @@ impl App {
|
|||
lv.footer_list.select(Some(0));
|
||||
}
|
||||
KeyCode::Esc => lv.input_state.reset(),
|
||||
KeyCode::Char('s') if !lv.stack.is_empty() => {
|
||||
KeyCode::Char('s') if !lv.view.cursor.toplevel() => {
|
||||
lv.input_state.target(InputTarget::Surround);
|
||||
}
|
||||
KeyCode::Char('t') => {
|
||||
|
|
@ -644,6 +649,7 @@ impl Widget for &mut App {
|
|||
|
||||
Items::new(
|
||||
items,
|
||||
&lv.filters,
|
||||
selected_offset,
|
||||
&lv.input_state,
|
||||
lv.footer_list.selected.and_then(|idx| {
|
||||
|
|
|
|||
204
src/tui/model.rs
204
src/tui/model.rs
|
|
@ -2,14 +2,19 @@ use std::{
|
|||
collections::BTreeMap,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
path::PathBuf,
|
||||
sync::{Arc, OnceLock},
|
||||
sync::{Mutex, OnceLock},
|
||||
thread,
|
||||
};
|
||||
|
||||
use dumpster::{Trace, TraceWith, Visitor, sync::Gc};
|
||||
use jiff::Timestamp;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::tui::widgets::line_text::LineText;
|
||||
use crate::tui::{
|
||||
filter::FilterKind, log_viewer::filters::Filters, processing::Cursor,
|
||||
widgets::line_text::LineText,
|
||||
};
|
||||
|
||||
pub fn pretty_print_value(v: &Value) -> String {
|
||||
match v {
|
||||
|
|
@ -23,6 +28,10 @@ pub fn pretty_print_value(v: &Value) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn id(input: &Gc<LogEntry>) -> usize {
|
||||
Gc::as_ptr(input) as usize
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Hash)]
|
||||
pub enum Level {
|
||||
#[serde(rename = "TRACE")]
|
||||
|
|
@ -37,24 +46,90 @@ pub enum Level {
|
|||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Trace)]
|
||||
pub struct ChildInfo {
|
||||
pub first_child: Option<Gc<LogEntry>>,
|
||||
pub last_child: Option<Gc<LogEntry>>,
|
||||
|
||||
pub prev: Option<Gc<LogEntry>>,
|
||||
pub next: OnceLock<Gc<LogEntry>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Trace)]
|
||||
pub enum LogEntry {
|
||||
Single {
|
||||
raw: RawLogEntry,
|
||||
},
|
||||
Sub {
|
||||
enter: RawLogEntry,
|
||||
sub_entries: Vec<Arc<LogEntry>>,
|
||||
exit: RawLogEntry,
|
||||
|
||||
count_sub: OnceLock<usize>,
|
||||
children: Gc<ChildInfo>,
|
||||
},
|
||||
Single {
|
||||
entry: RawLogEntry,
|
||||
prev: Option<Gc<LogEntry>>,
|
||||
next: OnceLock<Gc<LogEntry>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl LogEntry {
|
||||
pub fn prev(&self) -> Option<Gc<LogEntry>> {
|
||||
match self {
|
||||
LogEntry::Sub { children, .. } => children.prev.clone(),
|
||||
LogEntry::Single { prev, .. } => prev.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Option<Gc<LogEntry>> {
|
||||
match self {
|
||||
LogEntry::Sub { children, .. } => children.next.get().cloned(),
|
||||
LogEntry::Single { next, .. } => next.get().cloned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_next(&self) -> &OnceLock<Gc<LogEntry>> {
|
||||
match self {
|
||||
LogEntry::Sub { children, .. } => &children.next,
|
||||
LogEntry::Single { next, .. } => next,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_child(&self) -> Option<Gc<LogEntry>> {
|
||||
match self {
|
||||
LogEntry::Sub { children, .. } => children.first_child.clone(),
|
||||
LogEntry::Single { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_child(&self) -> Option<Gc<LogEntry>> {
|
||||
match self {
|
||||
LogEntry::Sub { children, .. } => children.last_child.clone(),
|
||||
LogEntry::Single { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_removed(&self, filters: &Filters) -> bool {
|
||||
for f in filters.get() {
|
||||
if let FilterKind::Remove = f.kind
|
||||
&& f.matcher.matches(self)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_inlined(&self, filters: &Filters) -> bool {
|
||||
for f in filters.get() {
|
||||
if let FilterKind::Inline = f.kind
|
||||
&& f.matcher.matches(self)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn file_line_string(&self) -> (PathBuf, usize) {
|
||||
let entry = match self {
|
||||
LogEntry::Single { raw } => raw,
|
||||
LogEntry::Single { entry, .. } => entry,
|
||||
LogEntry::Sub { enter, .. } => enter,
|
||||
};
|
||||
|
||||
|
|
@ -68,8 +143,8 @@ impl LogEntry {
|
|||
pub fn hash(&self) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
match self {
|
||||
LogEntry::Single { raw } => {
|
||||
raw.hash(&mut hasher);
|
||||
LogEntry::Single { entry, .. } => {
|
||||
entry.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
}
|
||||
LogEntry::Sub { enter, exit, .. } => {
|
||||
|
|
@ -81,7 +156,7 @@ impl LogEntry {
|
|||
|
||||
pub fn all_fields(&self) -> LogFields {
|
||||
match self {
|
||||
LogEntry::Single { raw } => raw.all_fields(),
|
||||
LogEntry::Single { entry, .. } => entry.all_fields(),
|
||||
LogEntry::Sub { enter, exit, .. } => enter.all_fields().merge(&exit.all_fields()),
|
||||
}
|
||||
}
|
||||
|
|
@ -93,22 +168,84 @@ impl LogEntry {
|
|||
res
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
pub fn all_children(&self, filters: &Filters) -> usize {
|
||||
match self {
|
||||
LogEntry::Single { .. } => 1,
|
||||
LogEntry::Sub {
|
||||
sub_entries,
|
||||
count_sub,
|
||||
..
|
||||
} => {
|
||||
*count_sub.get_or_init(|| sub_entries.iter().map(|i| i.count()).sum::<usize>() + 1)
|
||||
Self::Single { .. } => 0,
|
||||
Self::Sub { children, .. } => {
|
||||
if let Some(first_child) = &children.first_child {
|
||||
let cached = {
|
||||
filters
|
||||
.all_children_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&id(&first_child))
|
||||
.copied()
|
||||
};
|
||||
if let Some(cached) = cached {
|
||||
cached
|
||||
} else {
|
||||
// TODO: threadpool
|
||||
let mut c = Cursor::new(first_child.clone());
|
||||
let mut count = 1;
|
||||
while c.next(filters) {
|
||||
count += c.curr().all_children(filters);
|
||||
count += 1;
|
||||
}
|
||||
filters
|
||||
.all_children_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id(&first_child), count);
|
||||
|
||||
count
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direct_children(&self, filters: &Filters) -> usize {
|
||||
match self {
|
||||
Self::Single { .. } => 0,
|
||||
Self::Sub { children, .. } => {
|
||||
if let Some(first_child) = &children.first_child {
|
||||
let cached = {
|
||||
filters
|
||||
.direct_children_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&id(&first_child))
|
||||
.copied()
|
||||
};
|
||||
if let Some(cached) = cached {
|
||||
cached
|
||||
} else {
|
||||
// TODO: threadpool
|
||||
let mut c = Cursor::new(first_child.clone());
|
||||
let mut count = 1;
|
||||
while c.next(filters) {
|
||||
count += 1;
|
||||
}
|
||||
filters
|
||||
.direct_children_cache
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(id(&first_child), count);
|
||||
|
||||
count
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_or_name(&self) -> Option<String> {
|
||||
match self {
|
||||
LogEntry::Single { raw } => raw.fields.message().map(|i| i.to_string()),
|
||||
LogEntry::Single { entry, .. } => entry.fields.message().map(|i| i.to_string()),
|
||||
LogEntry::Sub { enter, .. } => {
|
||||
if let Some(val) = enter.all_fields().fields.get("name") {
|
||||
Some(val.clone())
|
||||
|
|
@ -119,7 +256,7 @@ impl LogEntry {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn line_text(&self, tree: String) -> LineText {
|
||||
pub fn line_text(&self, tree: String, filters: &Filters) -> LineText {
|
||||
const NO_MESSAGE: &str = "<no message>";
|
||||
const SPACES_BEFORE: &str = " ";
|
||||
|
||||
|
|
@ -139,26 +276,26 @@ impl LogEntry {
|
|||
};
|
||||
|
||||
match self {
|
||||
LogEntry::Single { raw } => LineText::new(
|
||||
LogEntry::Single { entry, .. } => LineText::new(
|
||||
SPACES_BEFORE.to_string(),
|
||||
single_field(raw),
|
||||
single_field(entry),
|
||||
self.message_or_name(),
|
||||
tree,
|
||||
),
|
||||
LogEntry::Sub {
|
||||
enter, sub_entries, ..
|
||||
enter, children, ..
|
||||
} => {
|
||||
if let Some(val) = enter.all_fields().fields.get("name") {
|
||||
let (prefix, sym) = if sub_entries.is_empty()
|
||||
|| sub_entries.get(0).is_some_and(|i| i.is_return())
|
||||
let (prefix, sym) = if children.first_child.is_none()
|
||||
|| children.first_child.as_ref().is_some_and(|i| i.is_return())
|
||||
{
|
||||
(SPACES_BEFORE.to_string(), "⟲")
|
||||
} else {
|
||||
(
|
||||
format!(
|
||||
"{:5}⭣{:5}⇊ ",
|
||||
sub_entries.len(),
|
||||
self.count().wrapping_sub(1)
|
||||
self.direct_children(filters),
|
||||
self.all_children(filters)
|
||||
),
|
||||
"↪",
|
||||
)
|
||||
|
|
@ -238,6 +375,13 @@ pub struct RawLogEntry {
|
|||
pub spans: Vec<LogFields>,
|
||||
}
|
||||
|
||||
// doesn't contain any Gc<T>!
|
||||
unsafe impl<V: Visitor> TraceWith<V> for RawLogEntry {
|
||||
fn accept(&self, _visitor: &mut V) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RawLogEntry {
|
||||
pub fn all_fields(&self) -> LogFields {
|
||||
let mut res = self.fields.clone();
|
||||
|
|
|
|||
|
|
@ -1,209 +1,262 @@
|
|||
use std::sync::Arc;
|
||||
use std::{mem, sync::Arc};
|
||||
|
||||
pub type FilterList = [Arc<Filter>];
|
||||
|
||||
use crate::tui::{
|
||||
filter::{Filter, FilterKind},
|
||||
model::LogEntry,
|
||||
};
|
||||
use dumpster::sync::Gc;
|
||||
use itertools::Itertools;
|
||||
|
||||
pub trait IntoLogStream {
|
||||
type Stream: LogStream;
|
||||
use crate::tui::{filter::Filter, log_viewer::filters::Filters, model::LogEntry};
|
||||
|
||||
fn from_end(self, inline_depth: usize) -> Option<Self::Stream>;
|
||||
fn from_start(self, inline_depth: usize) -> Option<Self::Stream>;
|
||||
#[derive(Clone)]
|
||||
pub struct CursorMeta {
|
||||
entry: Gc<LogEntry>,
|
||||
continue_in_parent: bool,
|
||||
}
|
||||
|
||||
pub struct LogEntryStream {
|
||||
inner: Arc<LogEntry>,
|
||||
curr: usize,
|
||||
inline_depth: usize,
|
||||
#[derive(Clone)]
|
||||
pub struct Cursor {
|
||||
parents: Vec<CursorMeta>,
|
||||
curr: CursorMeta,
|
||||
}
|
||||
|
||||
impl LogStream for LogEntryStream {
|
||||
fn next(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
|
||||
return None;
|
||||
impl Cursor {
|
||||
pub fn new(start: Gc<LogEntry>) -> Self {
|
||||
Self {
|
||||
parents: Vec::new(),
|
||||
curr: CursorMeta {
|
||||
entry: start,
|
||||
continue_in_parent: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_with_parents(&mut self, filters: &Filters) -> bool {
|
||||
// join the current cursor to the parents
|
||||
let mut all_cursors = {
|
||||
let mut all_cursors = self.parents.clone();
|
||||
all_cursors.push(self.curr.clone());
|
||||
all_cursors
|
||||
};
|
||||
|
||||
let res = sub_entries.get(self.curr)?;
|
||||
self.curr += 1;
|
||||
Some((Arc::clone(res), self.inline_depth))
|
||||
// Update inline information, if the parent node is inlined,
|
||||
// Then after we're done with the current list we should continue in the parent.
|
||||
let mut prev_inlined = false;
|
||||
for curr in all_cursors.iter_mut() {
|
||||
if prev_inlined {
|
||||
curr.continue_in_parent = true;
|
||||
}
|
||||
prev_inlined = curr.entry.is_inlined(filters);
|
||||
}
|
||||
|
||||
// keep all present ones and the first one that's (possibly) removed
|
||||
let mut present: Vec<_> = all_cursors
|
||||
.into_iter()
|
||||
.take_while_inclusive(|i| i.entry.is_removed(filters))
|
||||
.collect();
|
||||
self.curr = present.pop().expect("always at least one");
|
||||
self.parents = present;
|
||||
|
||||
self.update(filters)
|
||||
}
|
||||
|
||||
fn prev(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
|
||||
return None;
|
||||
pub fn update(&mut self, filters: &Filters) -> bool {
|
||||
// make a backup now
|
||||
let old = self.clone();
|
||||
// if the current one is removed...
|
||||
if self.curr_is_removed(filters) {
|
||||
// try going forwards to the next visible node
|
||||
if self.next(filters) {
|
||||
return true;
|
||||
}
|
||||
// if there are none, go backwards instead
|
||||
*self = old.clone();
|
||||
if self.prev(filters) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise the entire layer must be empty, and we're forced to step out.
|
||||
// This is fine, there's always something on the stack that wasn't filtered out
|
||||
// because of the previous take_while_inclusive.
|
||||
*self = old.clone();
|
||||
if !self.exit(filters) {
|
||||
// unless the filter deleted everything in the root,
|
||||
// then there literally is nowhere to go.
|
||||
// TODO: nicer error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn curr(&self) -> Gc<LogEntry> {
|
||||
self.curr.entry.clone()
|
||||
}
|
||||
|
||||
fn curr_is_removed(&self, filters: &Filters) -> bool {
|
||||
self.curr().is_removed(filters)
|
||||
}
|
||||
|
||||
fn curr_is_inlined(&self, filters: &Filters) -> bool {
|
||||
self.curr().is_inlined(filters)
|
||||
}
|
||||
|
||||
fn exit_internal(&mut self) -> bool {
|
||||
let Some(new) = self.parents.pop() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if self.curr == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.curr -= 1;
|
||||
let res = sub_entries.get(self.curr)?;
|
||||
Some((Arc::clone(res), self.inline_depth))
|
||||
self.curr = new;
|
||||
true
|
||||
}
|
||||
|
||||
fn clone(&self) -> Box<dyn LogStream> {
|
||||
Box::new(Self {
|
||||
inner: Arc::clone(&self.inner),
|
||||
curr: self.curr,
|
||||
inline_depth: self.inline_depth,
|
||||
})
|
||||
fn enter_start_internal(&mut self) -> bool {
|
||||
let Some(new) = self.curr().first_child() else {
|
||||
return false;
|
||||
};
|
||||
self.parents.push(mem::replace(
|
||||
&mut self.curr,
|
||||
CursorMeta {
|
||||
entry: new,
|
||||
continue_in_parent: false,
|
||||
},
|
||||
));
|
||||
true
|
||||
}
|
||||
|
||||
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
|
||||
Some((Arc::clone(&self.inner), self.inline_depth))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLogStream for &Arc<LogEntry> {
|
||||
type Stream = LogEntryStream;
|
||||
|
||||
fn from_end(self, inline_depth: usize) -> Option<Self::Stream> {
|
||||
if let LogEntry::Sub { sub_entries, .. } = self.as_ref() {
|
||||
Some(LogEntryStream {
|
||||
inner: Arc::clone(&self),
|
||||
curr: sub_entries.len(),
|
||||
inline_depth,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
fn enter_end_internal(&mut self) -> bool {
|
||||
let Some(new) = self.curr().last_child() else {
|
||||
return false;
|
||||
};
|
||||
self.parents.push(mem::replace(
|
||||
&mut self.curr,
|
||||
CursorMeta {
|
||||
entry: new,
|
||||
continue_in_parent: false,
|
||||
},
|
||||
));
|
||||
true
|
||||
}
|
||||
|
||||
fn from_start(self, inline_depth: usize) -> Option<Self::Stream> {
|
||||
if let LogEntry::Sub { .. } = self.as_ref() {
|
||||
Some(LogEntryStream {
|
||||
inner: Arc::clone(&self),
|
||||
curr: 0,
|
||||
inline_depth,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LogStream {
|
||||
fn next(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)>;
|
||||
fn prev(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)>;
|
||||
|
||||
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)>;
|
||||
|
||||
fn clone(&self) -> Box<dyn LogStream>;
|
||||
|
||||
fn filter(&self, filter: Arc<Filter>) -> FilteredLogStream {
|
||||
FilteredLogStream {
|
||||
filter: filter,
|
||||
stack: vec![(self.enclosing_log_entry(), self.clone())],
|
||||
}
|
||||
fn prev_internal(&mut self) -> bool {
|
||||
let Some(new) = self.curr().prev() else {
|
||||
return false;
|
||||
};
|
||||
self.curr.entry = new;
|
||||
true
|
||||
}
|
||||
|
||||
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
|
||||
fn next_internal(&mut self) -> bool {
|
||||
let Some(new) = self.curr().next() else {
|
||||
return false;
|
||||
};
|
||||
self.curr.entry = new;
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FilteredLogStream {
|
||||
filter: Arc<Filter>,
|
||||
stack: Vec<(Option<(Arc<LogEntry>, usize)>, Box<dyn LogStream>)>,
|
||||
}
|
||||
|
||||
macro_rules! generate_candidate {
|
||||
($_self: tt, $iter_method: ident, $fl: ident, $forwards: expr) => {
|
||||
fn prev_not_removed(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
let stack_len = $_self.stack.len();
|
||||
let (_enclosing, top) = $_self.stack.last_mut().unwrap();
|
||||
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 {
|
||||
// Otherwise, try popping the stack once and try again
|
||||
$_self.stack.pop();
|
||||
} else {
|
||||
// However, never pop the stack empty.
|
||||
// If that'd happen, just return None.
|
||||
return None;
|
||||
if !self.prev_internal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.curr_is_removed(filters) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! generate_filter {
|
||||
($_self: tt, $candidate: ident, $fl: ident, $into_iter: ident, $forwards: expr) => {
|
||||
fn next_not_removed(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
let (elem, inline_depth) = $_self.$candidate($fl)?;
|
||||
if !self.next_internal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Filter { matcher, kind } = $_self.filter.as_ref();
|
||||
|
||||
if matcher.matches(&elem) {
|
||||
match kind {
|
||||
FilterKind::Inline => {
|
||||
// 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)), iter));
|
||||
}
|
||||
// Continue so we actually return a nested item.
|
||||
if $forwards {
|
||||
// return Some((elem, inline_depth));
|
||||
continue;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
FilterKind::Remove => {
|
||||
// continue past removed items
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some((elem, inline_depth));
|
||||
if !self.curr_is_removed(filters) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl FilteredLogStream {
|
||||
fn next_candidate(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
generate_candidate!(self, next, fl, true)
|
||||
}
|
||||
fn prev_candidate(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
generate_candidate!(self, prev, fl, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl LogStream for FilteredLogStream {
|
||||
fn next(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
generate_filter!(self, next_candidate, fl, from_start, true)
|
||||
}
|
||||
|
||||
fn prev(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
generate_filter!(self, prev_candidate, fl, from_end, false)
|
||||
}
|
||||
|
||||
fn clone(&self) -> Box<dyn LogStream> {
|
||||
Box::new(Self {
|
||||
filter: Arc::clone(&self.filter),
|
||||
stack: self
|
||||
.stack
|
||||
.iter()
|
||||
.map(|(enclosing, i)| (enclosing.clone(), LogStream::clone(i.as_ref())))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
|
||||
self.stack[0].0.clone()
|
||||
|
||||
fn prev_cont_in_parent(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
if self.prev_not_removed(filters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !self.curr.continue_in_parent {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.exit_internal();
|
||||
}
|
||||
}
|
||||
|
||||
fn next_cont_in_parent(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
if self.next_not_removed(filters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if !self.curr.continue_in_parent {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.exit_internal();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
if !self.prev_cont_in_parent(filters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.curr_is_inlined(filters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.enter_end_internal();
|
||||
self.curr.continue_in_parent = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, filters: &Filters) -> bool {
|
||||
loop {
|
||||
if !self.next_cont_in_parent(filters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.curr_is_inlined(filters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.enter_start_internal();
|
||||
self.curr.continue_in_parent = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enter(&mut self, filters: &Filters) -> bool {
|
||||
let before = self.clone();
|
||||
if !self.enter_start_internal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.update(filters) {
|
||||
*self = before;
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn exit(&mut self, filters: &Filters) -> bool {
|
||||
if !self.exit_internal() {
|
||||
return false;
|
||||
}
|
||||
self.update(filters);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn toplevel(&self) -> bool {
|
||||
self.parents.is_empty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,18 +4,17 @@ use std::{
|
|||
io::{self, BufRead, BufReader},
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
OnceLock,
|
||||
mpsc::{Receiver, sync_channel},
|
||||
mpsc::{Receiver, channel},
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
model::{LogEntry, RawLogEntry},
|
||||
processing::{FilterList, LogStream},
|
||||
};
|
||||
use dumpster::sync::Gc;
|
||||
|
||||
use crate::tui::model::{ChildInfo, LogEntry, RawLogEntry};
|
||||
|
||||
struct LogFileEntryGenerator {
|
||||
file: BufReader<File>,
|
||||
|
|
@ -45,38 +44,61 @@ impl LogFileEntryGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
fn next_entry(&mut self) -> Option<Arc<LogEntry>> {
|
||||
let mut stack = Vec::<(RawLogEntry, Vec<Arc<LogEntry>>)>::new();
|
||||
let mut curr = Vec::<Arc<LogEntry>>::new();
|
||||
fn next_entry(&mut self, prev: Option<Gc<LogEntry>>) -> Option<Gc<LogEntry>> {
|
||||
let mut stack = Vec::<(RawLogEntry, Option<Gc<LogEntry>>, Option<Gc<LogEntry>>)>::new();
|
||||
let mut curr_first = None;
|
||||
let mut curr_last = prev;
|
||||
|
||||
loop {
|
||||
let entry = self.next_raw_entry()?;
|
||||
|
||||
let new_entry = Arc::new(match entry.fields.message() {
|
||||
let new_entry = Gc::new(match entry.fields.message() {
|
||||
Some("enter") => {
|
||||
stack.push((entry, mem::take(&mut curr)));
|
||||
stack.push((entry, curr_first.take(), curr_last.take()));
|
||||
continue;
|
||||
}
|
||||
Some("exit") => {
|
||||
// TODO: does it match?
|
||||
let Some((enter, prev)) = stack.pop() else {
|
||||
let Some((enter, prev_first, prev_last)) = stack.pop() else {
|
||||
panic!("exit before entry");
|
||||
};
|
||||
let sub_entries = mem::replace(&mut curr, prev);
|
||||
let first_child = mem::replace(&mut curr_first, prev_first);
|
||||
let last_child = mem::replace(&mut curr_last, prev_last);
|
||||
|
||||
let prev = curr_last.clone();
|
||||
let next = OnceLock::new();
|
||||
|
||||
LogEntry::Sub {
|
||||
enter: enter,
|
||||
sub_entries,
|
||||
enter,
|
||||
exit: entry,
|
||||
count_sub: OnceLock::new(),
|
||||
children: Gc::new(ChildInfo {
|
||||
first_child,
|
||||
last_child,
|
||||
prev,
|
||||
next,
|
||||
}),
|
||||
}
|
||||
}
|
||||
_ => LogEntry::Single { raw: entry },
|
||||
_ => {
|
||||
let prev = curr_last.clone();
|
||||
let next = OnceLock::new();
|
||||
|
||||
LogEntry::Single { entry, prev, next }
|
||||
}
|
||||
});
|
||||
|
||||
if stack.is_empty() {
|
||||
return Some(new_entry);
|
||||
} else if let Some(last) = &mut curr_last {
|
||||
last.initialize_next().get_or_init({
|
||||
let new_entry = new_entry.clone();
|
||||
move || new_entry
|
||||
});
|
||||
*last = new_entry;
|
||||
} else {
|
||||
curr.push(new_entry);
|
||||
assert!(curr_first.is_none());
|
||||
curr_first = Some(new_entry.clone());
|
||||
curr_last = Some(new_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,30 +107,38 @@ impl LogFileEntryGenerator {
|
|||
struct Inner {
|
||||
pub path: PathBuf,
|
||||
|
||||
entry_generator: Receiver<Arc<LogEntry>>,
|
||||
cached_entries: Vec<Arc<LogEntry>>,
|
||||
first: Option<Gc<LogEntry>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LogfileReader(Arc<RefCell<Inner>>);
|
||||
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
||||
|
||||
impl LogfileReader {
|
||||
pub fn new(p: &Path) -> io::Result<Self> {
|
||||
let (tx, rx) = sync_channel(1000);
|
||||
let file = File::open(p)?;
|
||||
|
||||
let mut generator = LogFileEntryGenerator {
|
||||
file: BufReader::new(File::open(p)?),
|
||||
file: BufReader::new(file),
|
||||
};
|
||||
|
||||
thread::spawn(move || {
|
||||
while let Some(i) = generator.next_entry() {
|
||||
tx.send(i).unwrap();
|
||||
}
|
||||
});
|
||||
let first = generator.next_entry(None);
|
||||
|
||||
Ok(Self(Arc::new(RefCell::new(Inner {
|
||||
if let Some(mut curr_last) = first.clone() {
|
||||
thread::spawn(move || {
|
||||
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
||||
assert!(new.prev().is_some());
|
||||
curr_last.initialize_next().get_or_init({
|
||||
let new = new.clone();
|
||||
|| new
|
||||
});
|
||||
curr_last = new;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self(Rc::new(RefCell::new(Inner {
|
||||
path: p.to_path_buf(),
|
||||
cached_entries: Vec::new(),
|
||||
entry_generator: rx,
|
||||
first,
|
||||
}))))
|
||||
}
|
||||
|
||||
|
|
@ -116,55 +146,7 @@ impl LogfileReader {
|
|||
self.0.borrow().path.clone()
|
||||
}
|
||||
|
||||
fn fill_buf_to_access_index(&mut self, n: usize) -> Option<Arc<LogEntry>> {
|
||||
while self.0.borrow().cached_entries.len() <= n {
|
||||
let entry = self.0.borrow().entry_generator.recv().ok()?;
|
||||
self.0.borrow_mut().cached_entries.push(entry);
|
||||
}
|
||||
|
||||
Some(Arc::clone(&self.0.borrow().cached_entries[n]))
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> LogFileReaderStream {
|
||||
LogFileReaderStream {
|
||||
reader: self.clone(),
|
||||
curr: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LogFileReaderStream {
|
||||
reader: LogfileReader,
|
||||
curr: usize,
|
||||
}
|
||||
|
||||
impl LogStream for LogFileReaderStream {
|
||||
fn next(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
let entry = self.reader.fill_buf_to_access_index(self.curr)?;
|
||||
self.curr += 1;
|
||||
|
||||
Some((entry, 0))
|
||||
}
|
||||
|
||||
fn prev(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
||||
if self.curr == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry = self.reader.fill_buf_to_access_index(self.curr)?;
|
||||
self.curr -= 1;
|
||||
|
||||
Some((entry, 0))
|
||||
}
|
||||
|
||||
fn clone(&self) -> Box<dyn LogStream> {
|
||||
Box::new(Self {
|
||||
reader: self.reader.clone(),
|
||||
curr: self.curr,
|
||||
})
|
||||
}
|
||||
|
||||
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
|
||||
None
|
||||
pub fn first(&self) -> Option<Gc<LogEntry>> {
|
||||
self.0.borrow().first.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
use std::{cell::OnceCell, iter, sync::Arc};
|
||||
use std::{cell::OnceCell, iter};
|
||||
|
||||
use dumpster::sync::Gc;
|
||||
use itertools::Itertools;
|
||||
use ratatui::widgets::{List, ListItem, Widget};
|
||||
use regex::Regex;
|
||||
|
||||
use crate::tui::{
|
||||
log_viewer::input::{FieldMatcher, InputState, InputTarget},
|
||||
log_viewer::{
|
||||
filters::Filters,
|
||||
input::{FieldMatcher, InputState, InputTarget},
|
||||
},
|
||||
model::LogEntry,
|
||||
widgets::{
|
||||
last_error::LastError,
|
||||
|
|
@ -19,7 +23,7 @@ pub struct TreeState {
|
|||
}
|
||||
|
||||
impl TreeState {
|
||||
pub fn from_items(items: &[(Arc<LogEntry>, usize)]) -> Self {
|
||||
pub fn from_items(items: &[(Gc<LogEntry>, usize)]) -> Self {
|
||||
let mut res = Vec::new();
|
||||
let mut curr = String::new();
|
||||
let mut prev_depth = 0;
|
||||
|
|
@ -87,9 +91,10 @@ impl TreeState {
|
|||
}
|
||||
|
||||
pub struct Items<'a> {
|
||||
items: Vec<(Arc<LogEntry>, usize)>,
|
||||
items: Vec<(Gc<LogEntry>, usize)>,
|
||||
selected_offset: usize,
|
||||
input_state: &'a InputState,
|
||||
filters: &'a Filters,
|
||||
|
||||
selected_footer_field: Option<(String, String)>,
|
||||
last_error: LastError,
|
||||
|
|
@ -97,13 +102,15 @@ pub struct Items<'a> {
|
|||
|
||||
impl<'a> Items<'a> {
|
||||
pub fn new(
|
||||
items: Vec<(Arc<LogEntry>, usize)>,
|
||||
items: Vec<(Gc<LogEntry>, usize)>,
|
||||
filters: &'a Filters,
|
||||
selected_offset: usize,
|
||||
input_state: &'a InputState,
|
||||
selected_footer_field: Option<(String, String)>,
|
||||
last_error: LastError,
|
||||
) -> Self {
|
||||
Self {
|
||||
filters,
|
||||
items,
|
||||
selected_offset,
|
||||
input_state,
|
||||
|
|
@ -112,8 +119,11 @@ impl<'a> Items<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> Option<&Arc<LogEntry>> {
|
||||
self.items.get(self.selected_offset).map(|(s, _)| s)
|
||||
pub fn selected(&self) -> Option<Gc<LogEntry>> {
|
||||
self.items
|
||||
.get(self.selected_offset)
|
||||
.map(|(s, _)| s)
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +143,7 @@ impl Widget for Styled<'_, &Items<'_>> {
|
|||
.enumerate()
|
||||
.zip(ts.tree_prefixes)
|
||||
.map(|((idx, entry), tree)| {
|
||||
let line_text = entry.line_text(tree);
|
||||
let line_text = entry.line_text(tree, self.filters);
|
||||
|
||||
let mut line = line_text.styled(&self.styles);
|
||||
if idx == self.selected_offset
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue