zooming
This commit is contained in:
parent
bb8f6bedf6
commit
4a6c1020f4
10 changed files with 113 additions and 34 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
|
@ -228,6 +228,12 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
|
||||
[[package]]
|
||||
name = "color-ansi"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e56460573dab12b4bb6dfdb0abbb021e35d6a2bbd925acd2f5a9accff5654a8e"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
|
|
@ -1163,6 +1169,16 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty-print"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ad516586f2191e7ce412b9b164e61ee6403638aa70e98672b978c1f448e63f"
|
||||
dependencies = [
|
||||
"color-ansi",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
|
|
@ -1430,6 +1446,7 @@ dependencies = [
|
|||
"jiff",
|
||||
"logparse",
|
||||
"nix 0.31.1",
|
||||
"pretty-print",
|
||||
"ratatui",
|
||||
"ratatui-themes",
|
||||
"regex",
|
||||
|
|
|
|||
|
|
@ -26,3 +26,4 @@ regex = "1"
|
|||
crossterm = "*"
|
||||
dumpster = "2.1"
|
||||
logparse = {path = "./logparse/", version="0.2.0"}
|
||||
pretty-print = "0.1"
|
||||
|
|
|
|||
|
|
@ -46,9 +46,13 @@ pub struct Span<'a> {
|
|||
}
|
||||
|
||||
/// Configuration options for [`into_spans`]
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct Config {
|
||||
/// Turn sequences of more than 1 space into exactly 1 space.
|
||||
pub collapse_space: bool,
|
||||
/// Pretty print: wrap at braces etc
|
||||
pub pretty_print: bool,
|
||||
}
|
||||
|
||||
pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::tui::{
|
|||
LogViewer,
|
||||
input::{FieldMatcher, InputTarget},
|
||||
},
|
||||
model::{FieldsName, LogEntry},
|
||||
model::{LogEntry, SpanDescriptor},
|
||||
};
|
||||
|
||||
mod serialize_regex {
|
||||
|
|
@ -56,7 +56,7 @@ impl MatcherValue {
|
|||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum Matcher {
|
||||
Field {
|
||||
span: FieldsName,
|
||||
span: SpanDescriptor,
|
||||
name: String,
|
||||
value: MatcherValue,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::tui::{
|
|||
input::{FieldMatcher, InputState, InputTarget},
|
||||
view::LogView,
|
||||
},
|
||||
model::{FieldsName, LogEntry, id},
|
||||
model::{LogEntry, SpanDescriptor, id},
|
||||
processing::Cursor,
|
||||
widgets::{fieldtree, last_error::LastError},
|
||||
};
|
||||
|
|
@ -54,7 +54,7 @@ impl LogViewer {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_selected_field(&self) -> Option<(FieldsName, String, String)> {
|
||||
pub fn get_selected_field(&self) -> Option<(SpanDescriptor, String, String)> {
|
||||
let (span_idx, name_idx) = self.field_state.get()?;
|
||||
let entry = self.selected().map(|(s, _)| s)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use crossterm::{
|
|||
},
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use logparse::{self as lp, Config, into_spans, parse_input};
|
||||
use ratatui_themes::{Color, Theme, ThemeName};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
|
@ -23,10 +25,11 @@ use crate::tui::{
|
|||
LogViewer,
|
||||
input::{FieldMatcher, InputState, InputTarget},
|
||||
},
|
||||
model::SpanDescriptor,
|
||||
reader::LogfileReader,
|
||||
widgets::{
|
||||
fieldtree::FieldTree, hyperlink::Hyperlink, items::Items, last_error::LastError,
|
||||
styled::IntoStyled,
|
||||
line_text::style_span, styled::IntoStyled,
|
||||
},
|
||||
};
|
||||
use ratatui::{
|
||||
|
|
@ -37,7 +40,7 @@ use ratatui::{
|
|||
prelude::CrosstermBackend,
|
||||
style::Style,
|
||||
symbols::merge::MergeStrategy,
|
||||
text::Line,
|
||||
text::{Line, Span},
|
||||
widgets::{
|
||||
Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap,
|
||||
},
|
||||
|
|
@ -64,6 +67,7 @@ const HELP_TEXT: &str = "Generic:
|
|||
-> / enter / l enter nested view
|
||||
|
||||
f toggle show active filters
|
||||
z zoom in on a selected field
|
||||
|
||||
───────────────────────────────────────────────────────
|
||||
targeting logs:
|
||||
|
|
@ -127,6 +131,11 @@ enum Tab {
|
|||
LogViewer(LogViewer),
|
||||
Empty,
|
||||
Help,
|
||||
Zoom {
|
||||
span: SpanDescriptor,
|
||||
name: String,
|
||||
value: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
|
|
@ -137,6 +146,11 @@ impl Tab {
|
|||
(Tab::LogViewer(_), Some(path)) => format!("logs of {}", path.display()),
|
||||
(Tab::LogViewer(_), None) => "logs".to_string(),
|
||||
(Tab::Help, _) => "help".to_string(),
|
||||
(Tab::Zoom { span, name, .. }, _) => match span {
|
||||
SpanDescriptor::Main => format!("{name}"),
|
||||
SpanDescriptor::Span(s) => format!("{name} in {s}"),
|
||||
SpanDescriptor::Numbered(n) => format!("{name} in span #{n}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -244,6 +258,7 @@ impl App {
|
|||
match self.tabs.last_mut().unwrap() {
|
||||
Tab::Help => {}
|
||||
Tab::Empty => {}
|
||||
Tab::Zoom { .. } => {}
|
||||
Tab::FileChooser {
|
||||
files,
|
||||
state,
|
||||
|
|
@ -327,6 +342,11 @@ impl App {
|
|||
|
||||
KeyCode::Char('u') => lv.undo(),
|
||||
KeyCode::Char('r') => lv.redo(),
|
||||
KeyCode::Char('z') => {
|
||||
if let Some((span, name, value)) = lv.get_selected_field() {
|
||||
self.push_tab(Tab::Zoom { span, name, value });
|
||||
}
|
||||
}
|
||||
|
||||
KeyCode::Tab => {
|
||||
lv.input_state.target(InputTarget::Fields(None));
|
||||
|
|
@ -592,6 +612,7 @@ impl Widget for &mut App {
|
|||
|
||||
let (footer_focused, header_focused) = match self.current_tab() {
|
||||
Tab::Help => (false, false),
|
||||
Tab::Zoom { .. } => (false, false),
|
||||
Tab::FileChooser { .. } => (false, true),
|
||||
Tab::LogViewer(lv) => {
|
||||
let target_fields =
|
||||
|
|
@ -716,6 +737,42 @@ impl Widget for &mut App {
|
|||
|
||||
Paragraph::new(HELP_TEXT).render(popup_area, buf);
|
||||
}
|
||||
Tab::Zoom { value, .. } => {
|
||||
Clear.render(popup_area, buf);
|
||||
let popup_area = {
|
||||
let block = Block::bordered()
|
||||
.title_top("zoom")
|
||||
.style(styles.default)
|
||||
.padding(Padding::symmetric(3, 1))
|
||||
.border_style(styles.border_highlighted);
|
||||
let inner = block.inner(popup_area);
|
||||
block.render(popup_area, buf);
|
||||
inner
|
||||
};
|
||||
|
||||
if let Ok(parsed) = parse_input(&value) {
|
||||
let spans = into_spans(
|
||||
parsed,
|
||||
Config {
|
||||
collapse_space: true,
|
||||
},
|
||||
);
|
||||
|
||||
let spans = spans
|
||||
.into_iter()
|
||||
.map(|lp::Span { text, kind }| {
|
||||
let span = Span::from(text.into_owned());
|
||||
|
||||
let style = style_span(kind, styles.default, &styles);
|
||||
span.style(style)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
Paragraph::new(Line::from(spans)).render(popup_area, buf);
|
||||
} else {
|
||||
Paragraph::new(value.clone()).render(popup_area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.last_error.clone().styled(&styles).render(error, buf);
|
||||
|
|
|
|||
|
|
@ -396,7 +396,7 @@ unsafe impl<V: Visitor> TraceWith<V> for RawLogEntry {
|
|||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Hash, Clone, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum FieldsName {
|
||||
pub enum SpanDescriptor {
|
||||
/// The main fields (not of a span) of a log entry
|
||||
Main,
|
||||
Span(String),
|
||||
|
|
@ -409,23 +409,23 @@ pub struct SpansRef<'a> {
|
|||
}
|
||||
|
||||
impl<'a> SpansRef<'a> {
|
||||
pub fn find(&self, span: &FieldsName, name: &str) -> Option<&String> {
|
||||
pub fn find(&self, span: &SpanDescriptor, name: &str) -> Option<&String> {
|
||||
self.named()
|
||||
.find(|(name, _)| name == span)
|
||||
.and_then(|(_, fields)| fields.get(name))
|
||||
}
|
||||
|
||||
pub fn named(&self) -> impl Iterator<Item = (FieldsName, &'a LogFields)> {
|
||||
iter::once((FieldsName::Main, self.main)).chain(self.spans.iter().rev().enumerate().map(
|
||||
|(idx, fields)| {
|
||||
pub fn named(&self) -> impl Iterator<Item = (SpanDescriptor, &'a LogFields)> {
|
||||
iter::once((SpanDescriptor::Main, self.main)).chain(
|
||||
self.spans.iter().rev().enumerate().map(|(idx, fields)| {
|
||||
(
|
||||
fields
|
||||
.name()
|
||||
.map(|i| FieldsName::Span(i.to_string()))
|
||||
.unwrap_or_else(|| FieldsName::Numbered(idx)),
|
||||
.map(|i| SpanDescriptor::Span(i.to_string()))
|
||||
.unwrap_or_else(|| SpanDescriptor::Numbered(idx)),
|
||||
fields,
|
||||
)
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ mod tests {
|
|||
use crate::tui::{
|
||||
filter::{Filter, FilterKind, Matcher, MatcherValue},
|
||||
log_viewer::filters::Filters,
|
||||
model::FieldsName,
|
||||
model::SpanDescriptor,
|
||||
processing::Cursor,
|
||||
widgets::last_error::LastError,
|
||||
};
|
||||
|
|
@ -311,7 +311,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "message".to_string(),
|
||||
value: MatcherValue::Exact("foo".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Remove,
|
||||
}));
|
||||
|
|
@ -337,7 +337,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "message".to_string(),
|
||||
value: MatcherValue::Exact("baz".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Remove,
|
||||
}));
|
||||
|
|
@ -368,7 +368,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
|
|
@ -415,7 +415,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
|
|
@ -465,7 +465,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest1".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
|
|
@ -473,7 +473,7 @@ mod tests {
|
|||
matcher: Matcher::Field {
|
||||
name: "name".to_string(),
|
||||
value: MatcherValue::Exact("nest2".to_string()),
|
||||
span: FieldsName::Main,
|
||||
span: SpanDescriptor::Main,
|
||||
},
|
||||
kind: FilterKind::Inline,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use tui_widget_list::{ListBuilder, ListState, ListView};
|
|||
use crate::tui::{
|
||||
block_around,
|
||||
log_viewer::LogViewer,
|
||||
model::{FieldsName, LogEntry, LogFields},
|
||||
model::{SpanDescriptor, LogEntry, LogFields},
|
||||
widgets::{line_text::style_span, styled::Styled},
|
||||
};
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ impl<'a> FieldTree<'a> {
|
|||
self.lv.selected().map(|(s, _)| s)
|
||||
}
|
||||
|
||||
fn current_span_fields(&mut self, spans: &[(FieldsName, &LogFields)]) -> Vec<(String, String)> {
|
||||
fn current_span_fields(&mut self, spans: &[(SpanDescriptor, &LogFields)]) -> Vec<(String, String)> {
|
||||
let mut selected = self.state().current_span.selected.unwrap_or(0);
|
||||
if selected > spans.len() {
|
||||
selected = spans.len().saturating_sub(1);
|
||||
|
|
@ -120,7 +120,7 @@ impl<'a> FieldTree<'a> {
|
|||
}
|
||||
|
||||
impl Styled<'_, &mut FieldTree<'_>> {
|
||||
fn areas(&self, area: Rect, buf: &mut Buffer, name: Option<&FieldsName>) -> (Rect, Rect, Rect) {
|
||||
fn areas(&self, area: Rect, buf: &mut Buffer, name: Option<&SpanDescriptor>) -> (Rect, Rect, Rect) {
|
||||
let [spans_area, fields_area] = Layout::horizontal([
|
||||
Constraint::Length(if self.spans_focussed() || !self.focussed {
|
||||
25
|
||||
|
|
@ -146,9 +146,9 @@ impl Styled<'_, &mut FieldTree<'_>> {
|
|||
self.styles,
|
||||
Some(if let Some(i) = name {
|
||||
match i {
|
||||
FieldsName::Main => "own fields".to_string(),
|
||||
FieldsName::Span(s) => format!("fields of {s}"),
|
||||
FieldsName::Numbered(idx) => format!("fields of span #{idx}"),
|
||||
SpanDescriptor::Main => "own fields".to_string(),
|
||||
SpanDescriptor::Span(s) => format!("fields of {s}"),
|
||||
SpanDescriptor::Numbered(idx) => format!("fields of span #{idx}"),
|
||||
}
|
||||
} else {
|
||||
"fields".to_string()
|
||||
|
|
@ -191,9 +191,9 @@ impl Widget for Styled<'_, &mut FieldTree<'_>> {
|
|||
};
|
||||
|
||||
let mut item = Line::from(match span_name {
|
||||
FieldsName::Main => "own fields".to_string(),
|
||||
FieldsName::Span(s) => s.to_string(),
|
||||
FieldsName::Numbered(idx) => format!("span #{idx}"),
|
||||
SpanDescriptor::Main => "own fields".to_string(),
|
||||
SpanDescriptor::Span(s) => s.to_string(),
|
||||
SpanDescriptor::Numbered(idx) => format!("span #{idx}"),
|
||||
});
|
||||
if cx.is_selected && self_focussed {
|
||||
item = item.style(self.styles.highlighted);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::tui::{
|
|||
filters::Filters,
|
||||
input::{FieldMatcher, InputState, InputTarget},
|
||||
},
|
||||
model::{FieldsName, LogEntry},
|
||||
model::{SpanDescriptor, LogEntry},
|
||||
widgets::{
|
||||
last_error::LastError,
|
||||
line_text::Highlighted,
|
||||
|
|
@ -23,7 +23,7 @@ pub struct Items<'a> {
|
|||
input_state: &'a InputState,
|
||||
filters: &'a Filters,
|
||||
|
||||
selected_footer_field: Option<(FieldsName, String, String)>,
|
||||
selected_footer_field: Option<(SpanDescriptor, String, String)>,
|
||||
last_error: LastError,
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ impl<'a> Items<'a> {
|
|||
filters: &'a Filters,
|
||||
selected_offset: usize,
|
||||
input_state: &'a InputState,
|
||||
selected_footer_field: Option<(FieldsName, String, String)>,
|
||||
selected_footer_field: Option<(SpanDescriptor, String, String)>,
|
||||
last_error: LastError,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue