better span view
This commit is contained in:
parent
43e40b61e3
commit
fdfc08e88b
12 changed files with 612 additions and 206 deletions
298
src/tui/widgets/fieldtree.rs
Normal file
298
src/tui/widgets/fieldtree.rs
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use dumpster::sync::Gc;
|
||||
use itertools::Itertools;
|
||||
use logparse::{self as lp, Config, SpanKind, into_spans, parse_input};
|
||||
use ratatui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Layout, Rect, Spacing},
|
||||
text::{Line, Span},
|
||||
widgets::{Paragraph, StatefulWidget, Widget, Wrap},
|
||||
};
|
||||
use ratatui_themes::Style;
|
||||
use tui_widget_list::{ListBuilder, ListState, ListView};
|
||||
|
||||
use crate::tui::{
|
||||
block_around,
|
||||
log_viewer::LogViewer,
|
||||
model::{FieldsName, LogEntry, LogFields},
|
||||
widgets::{line_text::style_span, styled::Styled},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
spans_focussed: bool,
|
||||
|
||||
current_span: ListState,
|
||||
current_field: ListState,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn home(&mut self) {
|
||||
if self.spans_focussed {
|
||||
self.current_span.select(Some(0));
|
||||
} else {
|
||||
self.current_field.select(Some(0));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn down(&mut self) {
|
||||
if self.spans_focussed {
|
||||
self.current_span.next();
|
||||
} else {
|
||||
self.current_field.next();
|
||||
}
|
||||
}
|
||||
pub fn up(&mut self) {
|
||||
if self.spans_focussed {
|
||||
self.current_span.previous();
|
||||
} else {
|
||||
self.current_field.previous();
|
||||
}
|
||||
}
|
||||
pub fn right(&mut self) {
|
||||
self.spans_focussed = false;
|
||||
}
|
||||
pub fn left(&mut self) {
|
||||
self.spans_focussed = true;
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<(usize, usize)> {
|
||||
self.current_span.selected.zip(self.current_field.selected)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FieldTree<'a> {
|
||||
focussed: bool,
|
||||
lv: &'a mut LogViewer,
|
||||
}
|
||||
|
||||
impl<'a> FieldTree<'a> {
|
||||
pub fn new(lv: &'a mut LogViewer, focussed: bool) -> Self {
|
||||
if lv.field_state.current_span.selected.is_none() {
|
||||
lv.field_state.current_span.select(Some(0));
|
||||
}
|
||||
if lv.field_state.current_field.selected.is_none() {
|
||||
lv.field_state.current_field.select(Some(0));
|
||||
}
|
||||
|
||||
Self { focussed, lv }
|
||||
}
|
||||
|
||||
fn state(&self) -> &State {
|
||||
&self.lv.field_state
|
||||
}
|
||||
|
||||
fn state_mut(&mut self) -> &mut State {
|
||||
&mut self.lv.field_state
|
||||
}
|
||||
|
||||
fn current_entry(&self) -> Option<Gc<LogEntry>> {
|
||||
self.lv.selected().map(|(s, _)| s)
|
||||
}
|
||||
|
||||
fn current_span_fields(&mut self, spans: &[(FieldsName, &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);
|
||||
self.state_mut().current_span.select(Some(selected));
|
||||
}
|
||||
|
||||
spans
|
||||
.get(selected)
|
||||
.map(|(_, fields)| {
|
||||
fields
|
||||
.fields
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect_vec()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn spans_focussed(&self) -> bool {
|
||||
self.focussed && self.state().spans_focussed
|
||||
}
|
||||
|
||||
fn fields_focussed(&self) -> bool {
|
||||
self.focussed && !self.state().spans_focussed
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled<'_, &mut FieldTree<'_>> {
|
||||
fn areas(&self, area: Rect, buf: &mut Buffer, name: Option<&FieldsName>) -> (Rect, Rect, Rect) {
|
||||
let [spans_area, fields_area] = Layout::horizontal([
|
||||
Constraint::Length(if self.spans_focussed() || !self.focussed {
|
||||
25
|
||||
} else {
|
||||
12
|
||||
}),
|
||||
Constraint::Fill(1),
|
||||
])
|
||||
.spacing(Spacing::Overlap(1))
|
||||
.areas(area);
|
||||
|
||||
let spans_area = block_around(
|
||||
spans_area,
|
||||
buf,
|
||||
self.spans_focussed(),
|
||||
self.styles,
|
||||
Some("spans"),
|
||||
);
|
||||
let fields_area = block_around(
|
||||
fields_area,
|
||||
buf,
|
||||
self.fields_focussed(),
|
||||
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}"),
|
||||
}
|
||||
} else {
|
||||
"fields".to_string()
|
||||
}),
|
||||
);
|
||||
|
||||
let [field_names, field_values] =
|
||||
Layout::horizontal([Constraint::Length(15), Constraint::Fill(1)]).areas(fields_area);
|
||||
|
||||
(spans_area, field_names, field_values)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Styled<'_, &mut FieldTree<'_>> {
|
||||
fn render(mut self, area: Rect, buf: &mut Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let self_focussed = self.focussed;
|
||||
|
||||
let entry = self.current_entry();
|
||||
let spans = entry
|
||||
.as_ref()
|
||||
.map(|i| i.spans().named().collect_vec())
|
||||
.unwrap_or_default();
|
||||
|
||||
let current_span_name = self
|
||||
.state()
|
||||
.current_span
|
||||
.selected
|
||||
.and_then(|i| spans.get(i))
|
||||
.map(|(i, _)| i);
|
||||
let (spans_area, field_names_area, field_values_area) =
|
||||
self.areas(area, buf, current_span_name);
|
||||
|
||||
let spans_list = {
|
||||
let builder = ListBuilder::new(|cx| {
|
||||
let Some((span_name, _)) = &spans.get(cx.index) else {
|
||||
return (Line::from(""), 1);
|
||||
};
|
||||
|
||||
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}"),
|
||||
});
|
||||
if cx.is_selected && self_focussed {
|
||||
item = item.style(self.styles.highlighted);
|
||||
} else {
|
||||
item = item.style(self.styles.default);
|
||||
}
|
||||
(item, 1)
|
||||
});
|
||||
|
||||
ListView::new(builder, spans.len())
|
||||
};
|
||||
|
||||
let (values_list, names_list) = {
|
||||
let values_width = field_values_area.width;
|
||||
let styles = self.styles;
|
||||
|
||||
let current_span_fields = self.current_span_fields(&spans);
|
||||
|
||||
let paragraph = move |field_value: &str, base_style: Style| {
|
||||
// TODO: cache this parse
|
||||
let spans = parse_input(field_value)
|
||||
.map(|i| {
|
||||
into_spans(
|
||||
i,
|
||||
Config {
|
||||
collapse_space: true,
|
||||
},
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|_| {
|
||||
vec![lp::Span {
|
||||
text: Cow::Borrowed(field_value),
|
||||
kind: SpanKind::Text,
|
||||
}]
|
||||
});
|
||||
|
||||
let spans = spans
|
||||
.into_iter()
|
||||
.map(|lp::Span { text, kind }| {
|
||||
let span = Span::from(text.into_owned());
|
||||
|
||||
let style = style_span(kind, base_style, styles);
|
||||
span.style(style)
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
Paragraph::new(Line::from(spans)).wrap(Wrap { trim: true })
|
||||
};
|
||||
|
||||
let num_fields = current_span_fields.len();
|
||||
|
||||
let values_builder = ListBuilder::new({
|
||||
let current_span_fields = current_span_fields.clone();
|
||||
move |cx| {
|
||||
let Some((_, field_value)) = ¤t_span_fields.get(cx.index) else {
|
||||
return (Paragraph::new(Span::from("")), 1);
|
||||
};
|
||||
|
||||
let base_style = if cx.is_selected && self_focussed {
|
||||
self.styles.highlighted
|
||||
} else {
|
||||
self.styles.default
|
||||
};
|
||||
|
||||
let item = paragraph(field_value, base_style);
|
||||
assert_eq!(cx.cross_axis_size, values_width);
|
||||
let height = item.line_count(values_width);
|
||||
|
||||
(item, height as u16)
|
||||
}
|
||||
});
|
||||
|
||||
let names_builder = ListBuilder::new({
|
||||
move |cx| {
|
||||
let Some((name, value)) = ¤t_span_fields.get(cx.index) else {
|
||||
return (Line::from(""), 1);
|
||||
};
|
||||
|
||||
let item = paragraph(value, self.styles.default);
|
||||
let height = item.line_count(values_width);
|
||||
|
||||
let mut item = Line::from(name.clone());
|
||||
if cx.is_selected && self_focussed {
|
||||
item = item.style(self.styles.highlighted);
|
||||
} else {
|
||||
item = item.style(self.styles.default);
|
||||
}
|
||||
(item, height as u16)
|
||||
}
|
||||
});
|
||||
|
||||
(
|
||||
ListView::new(values_builder, num_fields),
|
||||
ListView::new(names_builder, num_fields),
|
||||
)
|
||||
};
|
||||
|
||||
spans_list.render(spans_area, buf, &mut self.state_mut().current_span);
|
||||
values_list.render(field_values_area, buf, &mut self.state_mut().current_field);
|
||||
names_list.render(field_names_area, buf, &mut self.state_mut().current_field);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue