From 430c62c12083c215067eaedf7863d500f6b109d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Fri, 20 Mar 2026 10:44:20 +0100 Subject: [PATCH] linked lists --- Cargo.lock | 185 ++++++++++++++++ Cargo.toml | 1 + src/main.rs | 2 +- src/tui/log_viewer/filters.rs | 17 +- src/tui/log_viewer/input.rs | 4 +- src/tui/log_viewer/mod.rs | 299 ++++++------------------- src/tui/log_viewer/view.rs | 26 +-- src/tui/mod.rs | 18 +- src/tui/model.rs | 204 ++++++++++++++--- src/tui/processing.rs | 401 +++++++++++++++++++--------------- src/tui/reader.rs | 144 ++++++------ src/tui/widgets/items.rs | 26 ++- 12 files changed, 783 insertions(+), 544 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cf6d04..1cc7a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,6 +151,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -378,6 +388,28 @@ dependencies = [ "litrs", ] +[[package]] +name = "dumpster" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb58706890edf17f8ea4e547867910939edabf1c6b307aa6e6781697e10642b" +dependencies = [ + "dumpster_derive", + "loom", + "parking_lot", +] + +[[package]] +name = "dumpster_derive" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d846a530d39b6eba9a28eb3682c622cf96c8e54548bf5831e8bf4935c3dd2b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "either" version = "1.15.0" @@ -430,6 +462,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "finl_unicode" version = "1.4.0" @@ -460,6 +498,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -716,6 +769,19 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "lru" version = "0.16.3" @@ -735,6 +801,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.8.0" @@ -809,6 +884,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num-conv" version = "0.2.0" @@ -983,6 +1067,12 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.13.1" @@ -1192,6 +1282,7 @@ version = "0.1.0" dependencies = [ "clap", "crossterm", + "dumpster", "itertools", "jiff", "nix 0.31.1", @@ -1238,6 +1329,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1304,6 +1401,21 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook" version = "0.3.18" @@ -1505,6 +1617,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -1526,6 +1647,55 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "tui-widget-list" version = "0.15.0" @@ -1601,6 +1771,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" @@ -1819,6 +1995,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 3911081..8c3d386 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ itertools = "0.14" nix = {version = "0.31", features = ["process", "signal"]} regex = "1" crossterm = "*" +dumpster = "2.1" diff --git a/src/main.rs b/src/main.rs index 4a9e553..536bb1d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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) = { diff --git a/src/tui/log_viewer/filters.rs b/src/tui/log_viewer/filters.rs index a184ae1..0b55a0f 100644 --- a/src/tui/log_viewer/filters.rs +++ b/src/tui/log_viewer/filters.rs @@ -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>, + + #[serde(skip)] + pub direct_children_cache: Arc>>, + #[serde(skip)] + pub all_children_cache: Arc>>, + 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) diff --git a/src/tui/log_viewer/input.rs b/src/tui/log_viewer/input.rs index 5b2beb1..b24e373 100644 --- a/src/tui/log_viewer/input.rs +++ b/src/tui/log_viewer/input.rs @@ -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(), } } } diff --git a/src/tui/log_viewer/mod.rs b/src/tui/log_viewer/mod.rs index c75f457..f884641 100644 --- a/src/tui/log_viewer/mod.rs +++ b/src/tui/log_viewer/mod.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, iter, mem, 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, - curr: LogView, cache: HashMap, LogView>, - pub root_stream: Box, + 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, 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 { - 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::>() - // ) - // .unwrap(); - - let find_elem_in_stream = - |stream: &dyn LogStream, elem: &Arc| -> Option> { - let mut temp_stream = stream.clone(); - while let Some((curr, _)) = temp_stream.next(self.filters.get()) { - if Arc::ptr_eq(&curr, elem) { - return Some(temp_stream); - } - } - - None - }; - - let mut current_stream = self.filtered_root_stream(); - let mut new_stack = Vec::::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::>() - // ) - // .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) { - 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, usize)>, usize)> { - let mut temp_iter = self.curr.iter.clone(); + pub fn items(&mut self, max: usize) -> Option<(Vec<(Gc, 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, usize)> { - self.curr.selected(self.filters.get()) + pub fn selected(&self) -> Option<(Gc, 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 { - 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 = diff --git a/src/tui/log_viewer/view.rs b/src/tui/log_viewer/view.rs index deda644..18df8a7 100644 --- a/src/tui/log_viewer/view.rs +++ b/src/tui/log_viewer/view.rs @@ -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, + pub cursor: Cursor, pub selection_offset: usize, } impl LogView { - pub fn selected(&self, fl: &FilterList) -> Option<(Arc, usize)> { - let mut temp_iter = self.iter.clone(); - for _ in 0..self.selection_offset { - let _ = temp_iter.next(fl)?; + pub fn selected(&self, filters: &Filters) -> Option<(Gc, 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, } } } diff --git a/src/tui/mod.rs b/src/tui/mod.rs index b5968e1..1461f67 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -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| { diff --git a/src/tui/model.rs b/src/tui/model.rs index 4b645d6..1313d2f 100644 --- a/src/tui/model.rs +++ b/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) -> 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>, + pub last_child: Option>, + + pub prev: Option>, + pub next: OnceLock>, +} + +#[derive(Debug, Trace)] pub enum LogEntry { - Single { - raw: RawLogEntry, - }, Sub { enter: RawLogEntry, - sub_entries: Vec>, exit: RawLogEntry, - - count_sub: OnceLock, + children: Gc, + }, + Single { + entry: RawLogEntry, + prev: Option>, + next: OnceLock>, }, } impl LogEntry { + pub fn prev(&self) -> Option> { + match self { + LogEntry::Sub { children, .. } => children.prev.clone(), + LogEntry::Single { prev, .. } => prev.clone(), + } + } + + pub fn next(&self) -> Option> { + match self { + LogEntry::Sub { children, .. } => children.next.get().cloned(), + LogEntry::Single { next, .. } => next.get().cloned(), + } + } + + pub fn initialize_next(&self) -> &OnceLock> { + match self { + LogEntry::Sub { children, .. } => &children.next, + LogEntry::Single { next, .. } => next, + } + } + + pub fn first_child(&self) -> Option> { + match self { + LogEntry::Sub { children, .. } => children.first_child.clone(), + LogEntry::Single { .. } => None, + } + } + + pub fn last_child(&self) -> Option> { + 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::() + 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 { 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 = ""; 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, } +// doesn't contain any Gc! +unsafe impl TraceWith for RawLogEntry { + fn accept(&self, _visitor: &mut V) -> Result<(), ()> { + Ok(()) + } +} + impl RawLogEntry { pub fn all_fields(&self) -> LogFields { let mut res = self.fields.clone(); diff --git a/src/tui/processing.rs b/src/tui/processing.rs index cb5c627..f9b685c 100644 --- a/src/tui/processing.rs +++ b/src/tui/processing.rs @@ -1,209 +1,262 @@ -use std::sync::Arc; +use std::{mem, sync::Arc}; pub type FilterList = [Arc]; -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; - fn from_start(self, inline_depth: usize) -> Option; +#[derive(Clone)] +pub struct CursorMeta { + entry: Gc, + continue_in_parent: bool, } -pub struct LogEntryStream { - inner: Arc, - curr: usize, - inline_depth: usize, +#[derive(Clone)] +pub struct Cursor { + parents: Vec, + curr: CursorMeta, } -impl LogStream for LogEntryStream { - fn next(&mut self, _fl: &FilterList) -> Option<(Arc, usize)> { - let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else { - return None; +impl Cursor { + pub fn new(start: Gc) -> 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, 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 { + 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 { - 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, usize)> { - Some((Arc::clone(&self.inner), self.inline_depth)) - } -} - -impl IntoLogStream for &Arc { - type Stream = LogEntryStream; - - fn from_end(self, inline_depth: usize) -> Option { - 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 { - 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, usize)>; - fn prev(&mut self, fl: &FilterList) -> Option<(Arc, usize)>; - - fn enclosing_log_entry(&self) -> Option<(Arc, usize)>; - - fn clone(&self) -> Box; - - fn filter(&self, filter: Arc) -> 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 { - 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, - stack: Vec<(Option<(Arc, usize)>, Box)>, -} - -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, usize)> { - generate_candidate!(self, next, fl, true) } - fn prev_candidate(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { - generate_candidate!(self, prev, fl, false) - } -} - -impl LogStream for FilteredLogStream { - fn next(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { - generate_filter!(self, next_candidate, fl, from_start, true) - } - - fn prev(&mut self, fl: &FilterList) -> Option<(Arc, usize)> { - generate_filter!(self, prev_candidate, fl, from_end, false) - } - - fn clone(&self) -> Box { - 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, 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() } } diff --git a/src/tui/reader.rs b/src/tui/reader.rs index b6d52ad..0ff88a9 100644 --- a/src/tui/reader.rs +++ b/src/tui/reader.rs @@ -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, @@ -45,38 +44,61 @@ impl LogFileEntryGenerator { } } - fn next_entry(&mut self) -> Option> { - let mut stack = Vec::<(RawLogEntry, Vec>)>::new(); - let mut curr = Vec::>::new(); + fn next_entry(&mut self, prev: Option>) -> Option> { + let mut stack = Vec::<(RawLogEntry, Option>, Option>)>::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>, - cached_entries: Vec>, + first: Option>, } #[derive(Clone)] -pub struct LogfileReader(Arc>); +pub struct LogfileReader(Rc>); impl LogfileReader { pub fn new(p: &Path) -> io::Result { - 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> { - 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, 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, 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 { - Box::new(Self { - reader: self.reader.clone(), - curr: self.curr, - }) - } - - fn enclosing_log_entry(&self) -> Option<(Arc, usize)> { - None + pub fn first(&self) -> Option> { + self.0.borrow().first.clone() } } diff --git a/src/tui/widgets/items.rs b/src/tui/widgets/items.rs index e44edb4..d54bd3e 100644 --- a/src/tui/widgets/items.rs +++ b/src/tui/widgets/items.rs @@ -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, usize)]) -> Self { + pub fn from_items(items: &[(Gc, 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, usize)>, + items: Vec<(Gc, 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, usize)>, + items: Vec<(Gc, 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> { - self.items.get(self.selected_offset).map(|(s, _)| s) + pub fn selected(&self) -> Option> { + 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