linked lists

This commit is contained in:
Jana Dönszelmann 2026-03-20 10:44:20 +01:00
parent 8eab2502c7
commit 430c62c120
No known key found for this signature in database
12 changed files with 783 additions and 544 deletions

185
Cargo.lock generated
View file

@ -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"

View file

@ -20,3 +20,4 @@ itertools = "0.14"
nix = {version = "0.31", features = ["process", "signal"]}
regex = "1"
crossterm = "*"
dumpster = "2.1"

View file

@ -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) = {

View file

@ -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)

View file

@ -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(),
}
}
}

View file

@ -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 =

View file

@ -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,
}
}
}

View file

@ -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| {

View file

@ -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();

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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