linked lists
This commit is contained in:
parent
8eab2502c7
commit
430c62c120
12 changed files with 783 additions and 544 deletions
185
Cargo.lock
generated
185
Cargo.lock
generated
|
|
@ -151,6 +151,16 @@ dependencies = [
|
||||||
"rustversion",
|
"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]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -378,6 +388,28 @@ dependencies = [
|
||||||
"litrs",
|
"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]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
|
@ -430,6 +462,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "finl_unicode"
|
name = "finl_unicode"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
@ -460,6 +498,21 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
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]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.7"
|
version = "0.14.7"
|
||||||
|
|
@ -716,6 +769,19 @@ version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
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]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.16.3"
|
version = "0.16.3"
|
||||||
|
|
@ -735,6 +801,15 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|
@ -809,6 +884,15 @@ dependencies = [
|
||||||
"minimal-lexical",
|
"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]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
@ -983,6 +1067,12 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
|
@ -1192,6 +1282,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"dumpster",
|
||||||
"itertools",
|
"itertools",
|
||||||
"jiff",
|
"jiff",
|
||||||
"nix 0.31.1",
|
"nix 0.31.1",
|
||||||
|
|
@ -1238,6 +1329,12 @@ version = "1.0.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
@ -1304,6 +1401,21 @@ dependencies = [
|
||||||
"digest",
|
"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]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.18"
|
version = "0.3.18"
|
||||||
|
|
@ -1505,6 +1617,15 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"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]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.47"
|
version = "0.3.47"
|
||||||
|
|
@ -1526,6 +1647,55 @@ version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
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]]
|
[[package]]
|
||||||
name = "tui-widget-list"
|
name = "tui-widget-list"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
@ -1601,6 +1771,12 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
|
|
@ -1819,6 +1995,15 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ itertools = "0.14"
|
||||||
nix = {version = "0.31", features = ["process", "signal"]}
|
nix = {version = "0.31", features = ["process", "signal"]}
|
||||||
regex = "1"
|
regex = "1"
|
||||||
crossterm = "*"
|
crossterm = "*"
|
||||||
|
dumpster = "2.1"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
"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::All => "debug".to_string(),
|
||||||
Preset::Crates { crates } => format!("{}", crates.join(",")),
|
Preset::Crates { crates } => crates.join(",").to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let (first, rest) = {
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -7,6 +12,12 @@ use crate::tui::{filter::Filter, widgets::last_error::LastError};
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Filters {
|
pub struct Filters {
|
||||||
filters: Vec<Arc<Filter>>,
|
filters: Vec<Arc<Filter>>,
|
||||||
|
|
||||||
|
#[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,
|
undo_pos: usize,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
@ -47,10 +58,14 @@ impl Filters {
|
||||||
undo_pos: 0,
|
undo_pos: 0,
|
||||||
path,
|
path,
|
||||||
error: Some(error),
|
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) {
|
fn filters_changed(&self) {
|
||||||
|
self.direct_children_cache.lock().unwrap().clear();
|
||||||
|
self.all_children_cache.lock().unwrap().clear();
|
||||||
match File::options()
|
match File::options()
|
||||||
.create(true)
|
.create(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ impl InputTarget {
|
||||||
Self::Fields(None) => "logs with a field...".to_string(),
|
Self::Fields(None) => "logs with a field...".to_string(),
|
||||||
Self::Fields(Some(fm)) => format!("logs with the selected field {}", fm.show()),
|
Self::Fields(Some(fm)) => format!("logs with the selected field {}", fm.show()),
|
||||||
Self::Text(fm) => format!("logs {}", fm.show()),
|
Self::Text(fm) => format!("logs {}", fm.show()),
|
||||||
Self::This => format!("this log"),
|
Self::This => "this log".to_string(),
|
||||||
Self::Surround => format!("the log surrounding the current view"),
|
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::{
|
use crate::tui::{
|
||||||
filter::Filter,
|
filter::Filter,
|
||||||
|
|
@ -8,9 +8,10 @@ use crate::tui::{
|
||||||
view::LogView,
|
view::LogView,
|
||||||
},
|
},
|
||||||
model::LogEntry,
|
model::LogEntry,
|
||||||
processing::{FilterList, IntoLogStream, LogStream},
|
processing::Cursor,
|
||||||
widgets::last_error::LastError,
|
widgets::last_error::LastError,
|
||||||
};
|
};
|
||||||
|
use dumpster::sync::Gc;
|
||||||
use tui_widget_list::ListState;
|
use tui_widget_list::ListState;
|
||||||
|
|
||||||
pub mod filters;
|
pub mod filters;
|
||||||
|
|
@ -18,11 +19,9 @@ pub mod input;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
pub struct LogViewer {
|
pub struct LogViewer {
|
||||||
pub stack: Vec<LogView>,
|
|
||||||
curr: LogView,
|
|
||||||
cache: HashMap<Vec<usize>, LogView>,
|
cache: HashMap<Vec<usize>, LogView>,
|
||||||
|
|
||||||
pub root_stream: Box<dyn LogStream>,
|
pub view: LogView,
|
||||||
|
|
||||||
pub last_height: usize,
|
pub last_height: usize,
|
||||||
pub last_offset: usize,
|
pub last_offset: usize,
|
||||||
|
|
@ -30,21 +29,18 @@ pub struct LogViewer {
|
||||||
pub last_fields_height: usize,
|
pub last_fields_height: usize,
|
||||||
|
|
||||||
pub footer_list: ListState,
|
pub footer_list: ListState,
|
||||||
filters: Filters,
|
pub filters: Filters,
|
||||||
pub input_state: InputState,
|
pub input_state: InputState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogViewer {
|
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 filters = Filters::new(filters_path, error);
|
||||||
let stream = stream.with_filters(filters.get());
|
|
||||||
Self {
|
Self {
|
||||||
stack: Vec::new(),
|
view: LogView {
|
||||||
curr: LogView {
|
cursor: Cursor::new(start),
|
||||||
iter: stream.clone(),
|
|
||||||
selection_offset: 0,
|
selection_offset: 0,
|
||||||
},
|
},
|
||||||
root_stream: stream.clone(),
|
|
||||||
cache: HashMap::new(),
|
cache: HashMap::new(),
|
||||||
footer_list: ListState::default(),
|
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>) {
|
pub fn add_filter(&mut self, filter: Arc<Filter>) {
|
||||||
let old_filters = self.filters.clone();
|
|
||||||
self.filters.push(Arc::clone(&filter));
|
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) {
|
pub fn update_num_items(&mut self, num_visible_items: usize) {
|
||||||
while self.curr.selection_offset >= num_visible_items {
|
while self.view.selection_offset >= num_visible_items {
|
||||||
if self.curr.selection_offset == 0 {
|
if self.view.selection_offset == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
self.curr.iter.next(self.filters.get());
|
self.view.cursor.next(&self.filters);
|
||||||
self.curr.selection_offset -= 1;
|
self.view.selection_offset -= 1;
|
||||||
}
|
}
|
||||||
self.last_height = num_visible_items;
|
self.last_height = num_visible_items;
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +76,7 @@ impl LogViewer {
|
||||||
if let Some((selected, _)) = self.selected() {
|
if let Some((selected, _)) = self.selected() {
|
||||||
let ret = match selected.as_ref() {
|
let ret = match selected.as_ref() {
|
||||||
LogEntry::Single { .. } => Default::default(),
|
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()
|
i.all_fields()
|
||||||
.get_key_value("return")
|
.get_key_value("return")
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.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)> {
|
pub fn items(&mut self, max: usize) -> Option<(Vec<(Gc<LogEntry>, usize)>, usize)> {
|
||||||
let mut temp_iter = self.curr.iter.clone();
|
let mut temp_iter = self.view.cursor.clone();
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for _ in 0..max {
|
for _ in 0..max {
|
||||||
let Some(i) = temp_iter.next(self.filters.get()) else {
|
if !temp_iter.next(&self.filters) {
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
res.push(i);
|
// TODO: inline depth
|
||||||
|
res.push((temp_iter.curr(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if res.len() > 0 && self.curr.selection_offset > res.len() - 1 {
|
if !res.is_empty() && self.view.selection_offset > res.len() - 1 {
|
||||||
self.curr.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) {
|
pub fn click(&mut self, row: u16) {
|
||||||
if row as usize >= self.last_offset {
|
if row as usize >= self.last_offset {
|
||||||
let row_in_list = row as usize - self.last_offset;
|
let row_in_list = row as usize - self.last_offset;
|
||||||
if row_in_list < self.last_height {
|
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.input_state = InputState::None;
|
||||||
self.enter();
|
self.enter();
|
||||||
} else {
|
} else {
|
||||||
self.curr.selection_offset = row_in_list;
|
self.view.selection_offset = row_in_list;
|
||||||
self.input_state = InputState::Target(InputTarget::This);
|
self.input_state = InputState::Target(InputTarget::This);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,8 +136,8 @@ impl LogViewer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected(&self) -> Option<(Arc<LogEntry>, usize)> {
|
pub fn selected(&self) -> Option<(Gc<LogEntry>, usize)> {
|
||||||
self.curr.selected(self.filters.get())
|
self.view.selected(&self.filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prev(&mut self) {
|
pub fn prev(&mut self) {
|
||||||
|
|
@ -299,10 +147,10 @@ impl LogViewer {
|
||||||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if self.curr.selection_offset == 0 {
|
if self.view.selection_offset == 0 {
|
||||||
let _ = self.curr.iter.prev(self.filters.get());
|
self.view.cursor.prev(&self.filters);
|
||||||
} else {
|
} else {
|
||||||
self.curr.selection_offset -= 1;
|
self.view.selection_offset -= 1;
|
||||||
}
|
}
|
||||||
self.input_state = InputState::None;
|
self.input_state = InputState::None;
|
||||||
}
|
}
|
||||||
|
|
@ -316,23 +164,23 @@ impl LogViewer {
|
||||||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.curr.selection_offset += 1;
|
self.view.selection_offset += 1;
|
||||||
self.input_state = InputState::None;
|
self.input_state = InputState::None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page_down(&mut self) {
|
pub fn page_down(&mut self) {
|
||||||
self.curr.selection_offset += self.last_height;
|
self.view.selection_offset += self.last_height;
|
||||||
self.input_state.reset();
|
self.input_state.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page_up(&mut self) {
|
pub fn page_up(&mut self) {
|
||||||
for _ in 0..self.last_height {
|
for _ in 0..self.last_height {
|
||||||
if self.curr.selection_offset == 0 {
|
if self.view.selection_offset == 0 {
|
||||||
let _ = self.curr.iter.prev(self.filters.get());
|
let _ = self.view.cursor.prev(&self.filters);
|
||||||
} else {
|
} else {
|
||||||
self.curr.selection_offset -= 1;
|
self.view.selection_offset -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.input_state.reset();
|
self.input_state.reset();
|
||||||
|
|
@ -345,67 +193,62 @@ impl LogViewer {
|
||||||
self.input_state = InputState::Target(InputTarget::Fields(None));
|
self.input_state = InputState::Target(InputTarget::Fields(None));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.curr.selection_offset = 0;
|
self.view.selection_offset = 0;
|
||||||
while self.curr.iter.prev(self.filters.get()).is_some() {}
|
while self.view.cursor.prev(&self.filters) {}
|
||||||
self.input_state = InputState::None;
|
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) {
|
pub fn back(&mut self) {
|
||||||
self.cache.insert(self.path(), self.curr.clone());
|
self.view.cursor.exit(&self.filters);
|
||||||
if let Some(stack) = self.stack.pop() {
|
// self.cache.insert(self.path(), self.curr.clone());
|
||||||
self.curr = stack;
|
|
||||||
}
|
|
||||||
self.input_state.reset();
|
self.input_state.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) {
|
pub fn undo(&mut self) {
|
||||||
let old_filters = self.filters.clone();
|
|
||||||
self.filters.undo();
|
self.filters.undo();
|
||||||
self.update_filters(old_filters.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) {
|
pub fn redo(&mut self) {
|
||||||
let old_filters = self.filters.clone();
|
|
||||||
self.filters.redo();
|
self.filters.redo();
|
||||||
self.update_filters(old_filters.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn enter(&mut self) {
|
pub fn enter(&mut self) {
|
||||||
match self.input_state {
|
match self.input_state {
|
||||||
InputState::None => {
|
InputState::None => {
|
||||||
let Some((s, _)) = self.selected() else {
|
for _ in 0..(self.view.selection_offset + 1) {
|
||||||
return;
|
self.view.cursor.next(&self.filters);
|
||||||
};
|
|
||||||
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(
|
self.view.cursor.enter(&self.filters);
|
||||||
&mut self.curr,
|
// let Some((s, _)) = self.selected() else {
|
||||||
LogView {
|
// return;
|
||||||
iter: i,
|
// };
|
||||||
selection_offset: 0,
|
// let Some(i) = s.from_start(0).map(|i| i.with_filters(self.filters.get())) else {
|
||||||
},
|
// return;
|
||||||
));
|
// };
|
||||||
if let Some(cached_view) = self.cache.get(&self.path()) {
|
//
|
||||||
self.curr = cached_view.clone();
|
// 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)) => {
|
InputState::Target(InputTarget::Fields(None)) => {
|
||||||
self.input_state =
|
self.input_state =
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
use std::sync::Arc;
|
use dumpster::sync::Gc;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{log_viewer::filters::Filters, model::LogEntry, processing::Cursor};
|
||||||
model::LogEntry,
|
|
||||||
processing::{FilterList, LogStream},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct LogView {
|
pub struct LogView {
|
||||||
pub iter: Box<dyn LogStream>,
|
pub cursor: Cursor,
|
||||||
pub selection_offset: usize,
|
pub selection_offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogView {
|
impl LogView {
|
||||||
pub fn selected(&self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
pub fn selected(&self, filters: &Filters) -> Option<(Gc<LogEntry>, usize)> {
|
||||||
let mut temp_iter = self.iter.clone();
|
let mut temp_iter = self.cursor.clone();
|
||||||
for _ in 0..self.selection_offset {
|
for _ in 0..(self.selection_offset + 1) {
|
||||||
let _ = temp_iter.next(fl)?;
|
if !temp_iter.next(filters) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_iter.next(fl)
|
// TODO: inline depth
|
||||||
|
Some((temp_iter.curr(), 0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for LogView {
|
impl Clone for LogView {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
iter: self.iter.clone(),
|
cursor: self.cursor.clone(),
|
||||||
selection_offset: self.selection_offset.clone(),
|
selection_offset: self.selection_offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,11 +214,16 @@ impl App {
|
||||||
match LogfileReader::new(&path) {
|
match LogfileReader::new(&path) {
|
||||||
Ok(i) => {
|
Ok(i) => {
|
||||||
self.current_file = Some(i.clone());
|
self.current_file = Some(i.clone());
|
||||||
|
|
||||||
|
if let Some(first) = i.first() {
|
||||||
self.replace_tab(Tab::LogViewer(LogViewer::new(
|
self.replace_tab(Tab::LogViewer(LogViewer::new(
|
||||||
i.iter(),
|
first,
|
||||||
filters_path,
|
filters_path,
|
||||||
self.last_error.clone(),
|
self.last_error.clone(),
|
||||||
)));
|
)));
|
||||||
|
} else {
|
||||||
|
panic!("no log entries");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
panic!()
|
panic!()
|
||||||
|
|
@ -319,7 +324,7 @@ impl App {
|
||||||
lv.footer_list.select(Some(0));
|
lv.footer_list.select(Some(0));
|
||||||
}
|
}
|
||||||
KeyCode::Esc => lv.input_state.reset(),
|
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);
|
lv.input_state.target(InputTarget::Surround);
|
||||||
}
|
}
|
||||||
KeyCode::Char('t') => {
|
KeyCode::Char('t') => {
|
||||||
|
|
@ -644,6 +649,7 @@ impl Widget for &mut App {
|
||||||
|
|
||||||
Items::new(
|
Items::new(
|
||||||
items,
|
items,
|
||||||
|
&lv.filters,
|
||||||
selected_offset,
|
selected_offset,
|
||||||
&lv.input_state,
|
&lv.input_state,
|
||||||
lv.footer_list.selected.and_then(|idx| {
|
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,
|
collections::BTreeMap,
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{Arc, OnceLock},
|
sync::{Mutex, OnceLock},
|
||||||
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use dumpster::{Trace, TraceWith, Visitor, sync::Gc};
|
||||||
use jiff::Timestamp;
|
use jiff::Timestamp;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
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 {
|
pub fn pretty_print_value(v: &Value) -> String {
|
||||||
match v {
|
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)]
|
#[derive(Deserialize, Debug, Hash)]
|
||||||
pub enum Level {
|
pub enum Level {
|
||||||
#[serde(rename = "TRACE")]
|
#[serde(rename = "TRACE")]
|
||||||
|
|
@ -37,24 +46,90 @@ pub enum Level {
|
||||||
Error,
|
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 {
|
pub enum LogEntry {
|
||||||
Single {
|
|
||||||
raw: RawLogEntry,
|
|
||||||
},
|
|
||||||
Sub {
|
Sub {
|
||||||
enter: RawLogEntry,
|
enter: RawLogEntry,
|
||||||
sub_entries: Vec<Arc<LogEntry>>,
|
|
||||||
exit: RawLogEntry,
|
exit: RawLogEntry,
|
||||||
|
children: Gc<ChildInfo>,
|
||||||
count_sub: OnceLock<usize>,
|
},
|
||||||
|
Single {
|
||||||
|
entry: RawLogEntry,
|
||||||
|
prev: Option<Gc<LogEntry>>,
|
||||||
|
next: OnceLock<Gc<LogEntry>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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) {
|
pub fn file_line_string(&self) -> (PathBuf, usize) {
|
||||||
let entry = match self {
|
let entry = match self {
|
||||||
LogEntry::Single { raw } => raw,
|
LogEntry::Single { entry, .. } => entry,
|
||||||
LogEntry::Sub { enter, .. } => enter,
|
LogEntry::Sub { enter, .. } => enter,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -68,8 +143,8 @@ impl LogEntry {
|
||||||
pub fn hash(&self) -> u64 {
|
pub fn hash(&self) -> u64 {
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
match self {
|
match self {
|
||||||
LogEntry::Single { raw } => {
|
LogEntry::Single { entry, .. } => {
|
||||||
raw.hash(&mut hasher);
|
entry.hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
LogEntry::Sub { enter, exit, .. } => {
|
LogEntry::Sub { enter, exit, .. } => {
|
||||||
|
|
@ -81,7 +156,7 @@ impl LogEntry {
|
||||||
|
|
||||||
pub fn all_fields(&self) -> LogFields {
|
pub fn all_fields(&self) -> LogFields {
|
||||||
match self {
|
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()),
|
LogEntry::Sub { enter, exit, .. } => enter.all_fields().merge(&exit.all_fields()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,22 +168,84 @@ impl LogEntry {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(&self) -> usize {
|
pub fn all_children(&self, filters: &Filters) -> usize {
|
||||||
match self {
|
match self {
|
||||||
LogEntry::Single { .. } => 1,
|
Self::Single { .. } => 0,
|
||||||
LogEntry::Sub {
|
Self::Sub { children, .. } => {
|
||||||
sub_entries,
|
if let Some(first_child) = &children.first_child {
|
||||||
count_sub,
|
let cached = {
|
||||||
..
|
filters
|
||||||
} => {
|
.all_children_cache
|
||||||
*count_sub.get_or_init(|| sub_entries.iter().map(|i| i.count()).sum::<usize>() + 1)
|
.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> {
|
pub fn message_or_name(&self) -> Option<String> {
|
||||||
match self {
|
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, .. } => {
|
LogEntry::Sub { enter, .. } => {
|
||||||
if let Some(val) = enter.all_fields().fields.get("name") {
|
if let Some(val) = enter.all_fields().fields.get("name") {
|
||||||
Some(val.clone())
|
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 NO_MESSAGE: &str = "<no message>";
|
||||||
const SPACES_BEFORE: &str = " ";
|
const SPACES_BEFORE: &str = " ";
|
||||||
|
|
||||||
|
|
@ -139,26 +276,26 @@ impl LogEntry {
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
LogEntry::Single { raw } => LineText::new(
|
LogEntry::Single { entry, .. } => LineText::new(
|
||||||
SPACES_BEFORE.to_string(),
|
SPACES_BEFORE.to_string(),
|
||||||
single_field(raw),
|
single_field(entry),
|
||||||
self.message_or_name(),
|
self.message_or_name(),
|
||||||
tree,
|
tree,
|
||||||
),
|
),
|
||||||
LogEntry::Sub {
|
LogEntry::Sub {
|
||||||
enter, sub_entries, ..
|
enter, children, ..
|
||||||
} => {
|
} => {
|
||||||
if let Some(val) = enter.all_fields().fields.get("name") {
|
if let Some(val) = enter.all_fields().fields.get("name") {
|
||||||
let (prefix, sym) = if sub_entries.is_empty()
|
let (prefix, sym) = if children.first_child.is_none()
|
||||||
|| sub_entries.get(0).is_some_and(|i| i.is_return())
|
|| children.first_child.as_ref().is_some_and(|i| i.is_return())
|
||||||
{
|
{
|
||||||
(SPACES_BEFORE.to_string(), "⟲")
|
(SPACES_BEFORE.to_string(), "⟲")
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
format!(
|
format!(
|
||||||
"{:5}⭣{:5}⇊ ",
|
"{:5}⭣{:5}⇊ ",
|
||||||
sub_entries.len(),
|
self.direct_children(filters),
|
||||||
self.count().wrapping_sub(1)
|
self.all_children(filters)
|
||||||
),
|
),
|
||||||
"↪",
|
"↪",
|
||||||
)
|
)
|
||||||
|
|
@ -238,6 +375,13 @@ pub struct RawLogEntry {
|
||||||
pub spans: Vec<LogFields>,
|
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 {
|
impl RawLogEntry {
|
||||||
pub fn all_fields(&self) -> LogFields {
|
pub fn all_fields(&self) -> LogFields {
|
||||||
let mut res = self.fields.clone();
|
let mut res = self.fields.clone();
|
||||||
|
|
|
||||||
|
|
@ -1,209 +1,262 @@
|
||||||
use std::sync::Arc;
|
use std::{mem, sync::Arc};
|
||||||
|
|
||||||
pub type FilterList = [Arc<Filter>];
|
pub type FilterList = [Arc<Filter>];
|
||||||
|
|
||||||
use crate::tui::{
|
use dumpster::sync::Gc;
|
||||||
filter::{Filter, FilterKind},
|
use itertools::Itertools;
|
||||||
model::LogEntry,
|
|
||||||
|
use crate::tui::{filter::Filter, log_viewer::filters::Filters, model::LogEntry};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CursorMeta {
|
||||||
|
entry: Gc<LogEntry>,
|
||||||
|
continue_in_parent: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Cursor {
|
||||||
|
parents: Vec<CursorMeta>,
|
||||||
|
curr: CursorMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IntoLogStream {
|
// Update inline information, if the parent node is inlined,
|
||||||
type Stream: LogStream;
|
// Then after we're done with the current list we should continue in the parent.
|
||||||
|
let mut prev_inlined = false;
|
||||||
fn from_end(self, inline_depth: usize) -> Option<Self::Stream>;
|
for curr in all_cursors.iter_mut() {
|
||||||
fn from_start(self, inline_depth: usize) -> Option<Self::Stream>;
|
if prev_inlined {
|
||||||
|
curr.continue_in_parent = true;
|
||||||
|
}
|
||||||
|
prev_inlined = curr.entry.is_inlined(filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LogEntryStream {
|
// keep all present ones and the first one that's (possibly) removed
|
||||||
inner: Arc<LogEntry>,
|
let mut present: Vec<_> = all_cursors
|
||||||
curr: usize,
|
.into_iter()
|
||||||
inline_depth: usize,
|
.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)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogStream for LogEntryStream {
|
pub fn update(&mut self, filters: &Filters) -> bool {
|
||||||
fn next(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
// make a backup now
|
||||||
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
|
let old = self.clone();
|
||||||
return None;
|
// 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;
|
||||||
};
|
};
|
||||||
|
self.curr = new;
|
||||||
let res = sub_entries.get(self.curr)?;
|
true
|
||||||
self.curr += 1;
|
|
||||||
Some((Arc::clone(res), self.inline_depth))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prev(&mut self, _fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
fn enter_start_internal(&mut self) -> bool {
|
||||||
let LogEntry::Sub { sub_entries, .. } = self.inner.as_ref() else {
|
let Some(new) = self.curr().first_child() else {
|
||||||
return None;
|
return false;
|
||||||
};
|
};
|
||||||
|
self.parents.push(mem::replace(
|
||||||
if self.curr == 0 {
|
&mut self.curr,
|
||||||
return None;
|
CursorMeta {
|
||||||
|
entry: new,
|
||||||
|
continue_in_parent: false,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
self.curr -= 1;
|
fn enter_end_internal(&mut self) -> bool {
|
||||||
let res = sub_entries.get(self.curr)?;
|
let Some(new) = self.curr().last_child() else {
|
||||||
Some((Arc::clone(res), self.inline_depth))
|
return false;
|
||||||
|
};
|
||||||
|
self.parents.push(mem::replace(
|
||||||
|
&mut self.curr,
|
||||||
|
CursorMeta {
|
||||||
|
entry: new,
|
||||||
|
continue_in_parent: false,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&self) -> Box<dyn LogStream> {
|
fn prev_internal(&mut self) -> bool {
|
||||||
Box::new(Self {
|
let Some(new) = self.curr().prev() else {
|
||||||
inner: Arc::clone(&self.inner),
|
return false;
|
||||||
curr: self.curr,
|
};
|
||||||
inline_depth: self.inline_depth,
|
self.curr.entry = new;
|
||||||
})
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enclosing_log_entry(&self) -> Option<(Arc<LogEntry>, usize)> {
|
fn next_internal(&mut self) -> bool {
|
||||||
Some((Arc::clone(&self.inner), self.inline_depth))
|
let Some(new) = self.curr().next() else {
|
||||||
}
|
return false;
|
||||||
|
};
|
||||||
|
self.curr.entry = new;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoLogStream for &Arc<LogEntry> {
|
fn prev_not_removed(&mut self, filters: &Filters) -> bool {
|
||||||
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 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 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
loop {
|
loop {
|
||||||
let stack_len = $_self.stack.len();
|
if !self.prev_internal() {
|
||||||
let (_enclosing, top) = $_self.stack.last_mut().unwrap();
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! generate_filter {
|
if !self.curr_is_removed(filters) {
|
||||||
($_self: tt, $candidate: ident, $fl: ident, $into_iter: ident, $forwards: expr) => {
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_not_removed(&mut self, filters: &Filters) -> bool {
|
||||||
loop {
|
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilteredLogStream {
|
if !self.curr_is_removed(filters) {
|
||||||
fn next_candidate(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
return true;
|
||||||
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 prev_cont_in_parent(&mut self, filters: &Filters) -> bool {
|
||||||
fn next(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
loop {
|
||||||
generate_filter!(self, next_candidate, fl, from_start, true)
|
if self.prev_not_removed(filters) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prev(&mut self, fl: &FilterList) -> Option<(Arc<LogEntry>, usize)> {
|
if !self.curr.continue_in_parent {
|
||||||
generate_filter!(self, prev_candidate, fl, from_end, false)
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone(&self) -> Box<dyn LogStream> {
|
self.exit_internal();
|
||||||
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)> {
|
fn next_cont_in_parent(&mut self, filters: &Filters) -> bool {
|
||||||
self.stack[0].0.clone()
|
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},
|
io::{self, BufRead, BufReader},
|
||||||
mem,
|
mem,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
OnceLock,
|
OnceLock,
|
||||||
mpsc::{Receiver, sync_channel},
|
mpsc::{Receiver, channel},
|
||||||
},
|
},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::{
|
use dumpster::sync::Gc;
|
||||||
model::{LogEntry, RawLogEntry},
|
|
||||||
processing::{FilterList, LogStream},
|
use crate::tui::model::{ChildInfo, LogEntry, RawLogEntry};
|
||||||
};
|
|
||||||
|
|
||||||
struct LogFileEntryGenerator {
|
struct LogFileEntryGenerator {
|
||||||
file: BufReader<File>,
|
file: BufReader<File>,
|
||||||
|
|
@ -45,38 +44,61 @@ impl LogFileEntryGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_entry(&mut self) -> Option<Arc<LogEntry>> {
|
fn next_entry(&mut self, prev: Option<Gc<LogEntry>>) -> Option<Gc<LogEntry>> {
|
||||||
let mut stack = Vec::<(RawLogEntry, Vec<Arc<LogEntry>>)>::new();
|
let mut stack = Vec::<(RawLogEntry, Option<Gc<LogEntry>>, Option<Gc<LogEntry>>)>::new();
|
||||||
let mut curr = Vec::<Arc<LogEntry>>::new();
|
let mut curr_first = None;
|
||||||
|
let mut curr_last = prev;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let entry = self.next_raw_entry()?;
|
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") => {
|
Some("enter") => {
|
||||||
stack.push((entry, mem::take(&mut curr)));
|
stack.push((entry, curr_first.take(), curr_last.take()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Some("exit") => {
|
Some("exit") => {
|
||||||
// TODO: does it match?
|
// 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");
|
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 {
|
LogEntry::Sub {
|
||||||
enter: enter,
|
enter,
|
||||||
sub_entries,
|
|
||||||
exit: entry,
|
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() {
|
if stack.is_empty() {
|
||||||
return Some(new_entry);
|
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 {
|
} 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 {
|
struct Inner {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
||||||
entry_generator: Receiver<Arc<LogEntry>>,
|
first: Option<Gc<LogEntry>>,
|
||||||
cached_entries: Vec<Arc<LogEntry>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LogfileReader(Arc<RefCell<Inner>>);
|
pub struct LogfileReader(Rc<RefCell<Inner>>);
|
||||||
|
|
||||||
impl LogfileReader {
|
impl LogfileReader {
|
||||||
pub fn new(p: &Path) -> io::Result<Self> {
|
pub fn new(p: &Path) -> io::Result<Self> {
|
||||||
let (tx, rx) = sync_channel(1000);
|
let file = File::open(p)?;
|
||||||
|
|
||||||
let mut generator = LogFileEntryGenerator {
|
let mut generator = LogFileEntryGenerator {
|
||||||
file: BufReader::new(File::open(p)?),
|
file: BufReader::new(file),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let first = generator.next_entry(None);
|
||||||
|
|
||||||
|
if let Some(mut curr_last) = first.clone() {
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Some(i) = generator.next_entry() {
|
while let Some(new) = generator.next_entry(Some(curr_last.clone())) {
|
||||||
tx.send(i).unwrap();
|
assert!(new.prev().is_some());
|
||||||
|
curr_last.initialize_next().get_or_init({
|
||||||
|
let new = new.clone();
|
||||||
|
|| new
|
||||||
|
});
|
||||||
|
curr_last = new;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self(Arc::new(RefCell::new(Inner {
|
Ok(Self(Rc::new(RefCell::new(Inner {
|
||||||
path: p.to_path_buf(),
|
path: p.to_path_buf(),
|
||||||
cached_entries: Vec::new(),
|
first,
|
||||||
entry_generator: rx,
|
|
||||||
}))))
|
}))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,55 +146,7 @@ impl LogfileReader {
|
||||||
self.0.borrow().path.clone()
|
self.0.borrow().path.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_buf_to_access_index(&mut self, n: usize) -> Option<Arc<LogEntry>> {
|
pub fn first(&self) -> Option<Gc<LogEntry>> {
|
||||||
while self.0.borrow().cached_entries.len() <= n {
|
self.0.borrow().first.clone()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
use std::{cell::OnceCell, iter, sync::Arc};
|
use std::{cell::OnceCell, iter};
|
||||||
|
|
||||||
|
use dumpster::sync::Gc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::widgets::{List, ListItem, Widget};
|
use ratatui::widgets::{List, ListItem, Widget};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
log_viewer::input::{FieldMatcher, InputState, InputTarget},
|
log_viewer::{
|
||||||
|
filters::Filters,
|
||||||
|
input::{FieldMatcher, InputState, InputTarget},
|
||||||
|
},
|
||||||
model::LogEntry,
|
model::LogEntry,
|
||||||
widgets::{
|
widgets::{
|
||||||
last_error::LastError,
|
last_error::LastError,
|
||||||
|
|
@ -19,7 +23,7 @@ pub struct TreeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 res = Vec::new();
|
||||||
let mut curr = String::new();
|
let mut curr = String::new();
|
||||||
let mut prev_depth = 0;
|
let mut prev_depth = 0;
|
||||||
|
|
@ -87,9 +91,10 @@ impl TreeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Items<'a> {
|
pub struct Items<'a> {
|
||||||
items: Vec<(Arc<LogEntry>, usize)>,
|
items: Vec<(Gc<LogEntry>, usize)>,
|
||||||
selected_offset: usize,
|
selected_offset: usize,
|
||||||
input_state: &'a InputState,
|
input_state: &'a InputState,
|
||||||
|
filters: &'a Filters,
|
||||||
|
|
||||||
selected_footer_field: Option<(String, String)>,
|
selected_footer_field: Option<(String, String)>,
|
||||||
last_error: LastError,
|
last_error: LastError,
|
||||||
|
|
@ -97,13 +102,15 @@ pub struct Items<'a> {
|
||||||
|
|
||||||
impl<'a> Items<'a> {
|
impl<'a> Items<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
items: Vec<(Arc<LogEntry>, usize)>,
|
items: Vec<(Gc<LogEntry>, usize)>,
|
||||||
|
filters: &'a Filters,
|
||||||
selected_offset: usize,
|
selected_offset: usize,
|
||||||
input_state: &'a InputState,
|
input_state: &'a InputState,
|
||||||
selected_footer_field: Option<(String, String)>,
|
selected_footer_field: Option<(String, String)>,
|
||||||
last_error: LastError,
|
last_error: LastError,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
filters,
|
||||||
items,
|
items,
|
||||||
selected_offset,
|
selected_offset,
|
||||||
input_state,
|
input_state,
|
||||||
|
|
@ -112,8 +119,11 @@ impl<'a> Items<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected(&self) -> Option<&Arc<LogEntry>> {
|
pub fn selected(&self) -> Option<Gc<LogEntry>> {
|
||||||
self.items.get(self.selected_offset).map(|(s, _)| s)
|
self.items
|
||||||
|
.get(self.selected_offset)
|
||||||
|
.map(|(s, _)| s)
|
||||||
|
.cloned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +143,7 @@ impl Widget for Styled<'_, &Items<'_>> {
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.zip(ts.tree_prefixes)
|
.zip(ts.tree_prefixes)
|
||||||
.map(|((idx, entry), tree)| {
|
.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);
|
let mut line = line_text.styled(&self.styles);
|
||||||
if idx == self.selected_offset
|
if idx == self.selected_offset
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue