diff --git a/src/tui/filter.rs b/src/tui/filter.rs index d9d0ee6..8f31845 100644 --- a/src/tui/filter.rs +++ b/src/tui/filter.rs @@ -90,9 +90,17 @@ impl Matcher { lv.selected().and_then(|(i, _)| i.message_or_name()), )?, }), - InputTarget::This => lv - .selected() - .map(|(i, _)| Self::Specific { hash: i.hash() }), + InputTarget::This => { + if lv + .selected() + .is_none_or(|(i, _)| !i.can_be_inlined(&lv.filters)) + { + return None; + } + + lv.selected() + .map(|(i, _)| Self::Specific { hash: i.hash() }) + } InputTarget::Surround => todo!(), } } diff --git a/src/tui/log_viewer/mod.rs b/src/tui/log_viewer/mod.rs index 2e361fc..fe17e52 100644 --- a/src/tui/log_viewer/mod.rs +++ b/src/tui/log_viewer/mod.rs @@ -102,7 +102,7 @@ impl LogViewer { break; } // TODO: inline depth - res.push((temp_iter.curr(), 0)); + res.push((temp_iter.curr(), temp_iter.inline_depth())); } if !res.is_empty() && self.view.selection_offset > res.len() - 1 { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 8a70af7..8b7c8e1 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -641,9 +641,9 @@ impl Widget for &mut App { { let full_file_path = canonical_rustc_root.join(&file); Hyperlink::new( - Line::from(format!("In file: {}", file.display())) + Line::from(format!("In file: {}:{line}", file.display())) .style(styles.default), - format!("file://{}:{line}", full_file_path.display()), + format!("file://{}", full_file_path.display()), ) .render(first_line, buf); } else { diff --git a/src/tui/model.rs b/src/tui/model.rs index 1cc40ff..6c9d7a9 100644 --- a/src/tui/model.rs +++ b/src/tui/model.rs @@ -115,9 +115,14 @@ impl LogEntry { false } + pub fn can_be_inlined(&self, filters: &Filters) -> bool { + self.can_enter(filters) + } + pub fn is_inlined(&self, filters: &Filters) -> bool { for f in filters.get() { if let FilterKind::Inline = f.kind + && self.can_be_inlined(filters) && f.matcher.matches(self) { return true; @@ -177,7 +182,7 @@ impl LogEntry { .all_children_cache .lock() .unwrap() - .get(&id(&first_child)) + .get(&id(first_child)) .copied() }; if let Some(cached) = cached { @@ -194,7 +199,7 @@ impl LogEntry { .all_children_cache .lock() .unwrap() - .insert(id(&first_child), count); + .insert(id(first_child), count); count } @@ -255,6 +260,16 @@ impl LogEntry { } } + pub fn has_only_return(&self) -> bool { + matches!(self, Self::Sub { children, .. } if children.first_child.as_ref().is_some_and(|i| i.is_return())) + } + + pub fn can_enter(&self, filters: &Filters) -> bool { + matches!(self, Self::Sub { children, .. } if children.first_child.is_some()) + && !self.has_only_return() + && self.all_children(filters) != 0 + } + pub fn line_text(&self, tree: String, filters: &Filters) -> LineText { const NO_MESSAGE: &str = ""; const SPACES_BEFORE: &str = " "; @@ -281,14 +296,12 @@ impl LogEntry { self.message_or_name(), tree, ), - LogEntry::Sub { - enter, children, .. - } => { + LogEntry::Sub { enter, .. } => { if let Some(val) = enter.all_fields().fields.get("name") { - let (prefix, sym) = if children.first_child.is_none() - || children.first_child.as_ref().is_some_and(|i| i.is_return()) - { + let (prefix, sym) = if self.has_only_return() { (SPACES_BEFORE.to_string(), "⟲") + } else if !self.can_enter(filters) { + (SPACES_BEFORE.to_string(), "") } else { ( format!( diff --git a/src/tui/processing.rs b/src/tui/processing.rs index 4a5a248..9898f71 100644 --- a/src/tui/processing.rs +++ b/src/tui/processing.rs @@ -1,4 +1,4 @@ -use std::mem; +use std::{iter, mem}; use dumpster::sync::Gc; use itertools::Itertools; @@ -62,14 +62,13 @@ impl Cursor { let old = self.clone(); // if the current one is inlined - if self.curr_is_inlined(filters) { - // try going one back - self.prev(filters); - self.next(filters); + while self.curr_is_inlined(filters) { + self.enter_start_internal(filters); + self.curr.continue_in_parent = true; } // if the current one is removed... - if self.curr_is_removed(filters) || self.curr_is_inlined(filters) { + if self.curr_is_removed(filters) { // try going forwards to the next visible node if self.next(filters) { return true; @@ -98,6 +97,15 @@ impl Cursor { self.curr.entry.clone() } + pub fn inline_depth(&self) -> usize { + self.parents + .iter() + .chain(iter::once(&self.curr)) + .rev() + .take_while(|i| i.continue_in_parent) + .count() + } + pub fn parent(&self) -> Option> { self.parents.last().map(|i| i.entry.clone()) } @@ -118,7 +126,11 @@ impl Cursor { true } - fn enter_start_internal(&mut self) -> bool { + fn enter_start_internal(&mut self, filters: &Filters) -> bool { + if !self.curr().can_enter(filters) { + return false; + } + let Some(new) = self.curr().first_child() else { return false; }; @@ -132,7 +144,11 @@ impl Cursor { true } - fn enter_end_internal(&mut self) -> bool { + fn enter_end_internal(&mut self, filters: &Filters) -> bool { + if !self.curr().can_enter(filters) { + return false; + } + let Some(new) = self.curr().last_child() else { return false; }; @@ -223,7 +239,7 @@ impl Cursor { return true; } - self.enter_end_internal(); + self.enter_end_internal(filters); self.curr.continue_in_parent = true; self.update(filters) } @@ -237,14 +253,14 @@ impl Cursor { return true; } - self.enter_start_internal(); + self.enter_start_internal(filters); self.curr.continue_in_parent = true; self.update(filters) } pub fn enter(&mut self, filters: &Filters) -> bool { let before = self.clone(); - if !self.enter_start_internal() { + if !self.enter_start_internal(filters) { return false; } diff --git a/src/tui/reader.rs b/src/tui/reader.rs index dfc53ef..86ae548 100644 --- a/src/tui/reader.rs +++ b/src/tui/reader.rs @@ -107,6 +107,7 @@ impl LogFileEntryGenerator { struct Inner { pub path: PathBuf, + #[allow(unused)] pub jh: Option>, first: Option>, } @@ -435,4 +436,42 @@ mod tests { assert_eq!(c.curr().message_or_name(), Some("foo".to_string())); assert!(!c.prev(&f)); } + + #[test] + fn inline_depth() { + let mut c = parse( + &[ + with_fields(r#"{"message": "enter", "name": "nest1"}"#), + with_fields(r#"{"message": "enter", "name": "nest2"}"#), + with_fields(r#"{"message": "baz"}"#), + with_fields(r#"{"message": "exit"}"#), + with_fields(r#"{"message": "exit"}"#), + ] + .join("\n"), + ); + let mut f = filters(); + c.enter(&f); + c.enter(&f); + assert_eq!(c.curr().message_or_name(), Some("baz".to_string())); + c.exit(&f); + c.exit(&f); + + f.push(Arc::new(Filter { + matcher: Matcher::Field { + name: "name".to_string(), + value: MatcherValue::Exact("nest1".to_string()), + }, + kind: FilterKind::Inline, + })); + f.push(Arc::new(Filter { + matcher: Matcher::Field { + name: "name".to_string(), + value: MatcherValue::Exact("nest2".to_string()), + }, + kind: FilterKind::Inline, + })); + c.update_with_parents(&f); + + assert_eq!(c.curr().message_or_name(), Some("baz".to_string())); + } } diff --git a/src/tui/widgets/items.rs b/src/tui/widgets/items.rs index d54bd3e..f655e7e 100644 --- a/src/tui/widgets/items.rs +++ b/src/tui/widgets/items.rs @@ -1,7 +1,6 @@ -use std::{cell::OnceCell, iter}; +use std::cell::OnceCell; use dumpster::sync::Gc; -use itertools::Itertools; use ratatui::widgets::{List, ListItem, Widget}; use regex::Regex; @@ -18,78 +17,6 @@ use crate::tui::{ }, }; -pub struct TreeState { - tree_prefixes: Vec, -} - -impl TreeState { - pub fn from_items(items: &[(Gc, usize)]) -> Self { - let mut res = Vec::new(); - let mut curr = String::new(); - let mut prev_depth = 0; - - for (depth, next_depth) in items - .iter() - .map(|i| i.1 as isize) - .chain(iter::once(-1isize)) - .tuple_windows() - { - if depth > prev_depth { - if depth > 1 { - let _ = curr.pop(); - let _ = curr.pop(); - curr.push('│'); - curr.push(' '); - } - for _ in prev_depth..depth.saturating_sub(2) { - curr.push(' '); - curr.push(' '); - } - if next_depth < depth { - curr.push('└'); - curr.push('─'); - } else { - curr.push('├'); - curr.push('─'); - } - } else if depth == prev_depth && next_depth < prev_depth { - let _ = curr.pop(); - let _ = curr.pop(); - curr.push('└'); - curr.push('─'); - } else if depth < prev_depth { - for _ in depth..prev_depth { - let _ = curr.pop(); - let _ = curr.pop(); - let _ = curr.pop(); - let _ = curr.pop(); - } - - if depth > 0 { - let _ = curr.pop(); - let _ = curr.pop(); - if next_depth < depth { - curr.push('└'); - curr.push('─'); - } else { - curr.push('├'); - curr.push('─'); - } - } - } - - prev_depth = depth; - if depth != 0 { - res.push(format!("{curr} ")); - } else { - res.push(String::new()); - } - } - - Self { tree_prefixes: res } - } -} - pub struct Items<'a> { items: Vec<(Gc, usize)>, selected_offset: usize, @@ -132,18 +59,25 @@ impl Widget for Styled<'_, &Items<'_>> { where Self: Sized, { - let ts = TreeState::from_items(&self.items); let regex_cache = OnceCell::new(); let list = List::new( self.inner .items .iter() - .map(|(entry, _)| entry) .enumerate() - .zip(ts.tree_prefixes) - .map(|((idx, entry), tree)| { - let line_text = entry.line_text(tree, self.filters); + .map(|(idx, (entry, depth))| { + let prefix = if *depth == 0 { + "".to_string() + } else { + let mut res = String::new(); + for _ in 0..*depth - 1 { + res.push_str(" "); + } + res.push_str("- "); + res + }; + let line_text = entry.line_text(prefix, self.filters); let mut line = line_text.styled(&self.styles); if idx == self.selected_offset