mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
event more grouping prep
This commit is contained in:
parent
0fa90510cb
commit
9e66967141
11 changed files with 149 additions and 145 deletions
13
src/app.rs
13
src/app.rs
|
|
@ -1,7 +1,5 @@
|
|||
use crate::grouping::{group_lines_by, FilterGrouping, LogGrouping};
|
||||
use crate::logfile::{LogFile, LogLine, LogLineNumber};
|
||||
use crate::logs::ParsedLogs;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::timegraph::{SparkLine, TimeGraph};
|
||||
use regex::{escape, Regex, RegexBuilder};
|
||||
use serde_json::Error as JsonError;
|
||||
|
|
@ -12,16 +10,11 @@ use time::OffsetDateTime;
|
|||
|
||||
pub struct App<'logs> {
|
||||
pub lines: &'logs ParsedLogs<'logs>,
|
||||
pub matches: &'logs [LogGrouping<'logs, MatchResult>],
|
||||
pub log_file: &'logs LogFile,
|
||||
pub unmatched_count: usize,
|
||||
}
|
||||
|
||||
impl<'logs> App<'logs> {
|
||||
pub fn match_lines(&self) -> usize {
|
||||
self.matches.len() + 1
|
||||
}
|
||||
|
||||
pub fn get_source_line(&self, index: LogLineNumber) -> Option<&'logs str> {
|
||||
self.log_file.nth(index)
|
||||
}
|
||||
|
|
@ -49,6 +42,7 @@ impl<'logs> App<'logs> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LineSet<'logs> {
|
||||
pub lines: Vec<&'logs LogLine<'logs>>,
|
||||
pub histogram: OnceCell<TimeGraph>,
|
||||
|
|
@ -99,11 +93,6 @@ impl<'logs> LineSet<'logs> {
|
|||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'logs LogLine<'logs>> + use<'a, 'logs> {
|
||||
self.lines.iter().copied()
|
||||
}
|
||||
|
||||
pub fn group_by<G: FilterGrouping>(&self) -> Vec<LogGrouping<G::Result>> {
|
||||
let _grouped = group_lines_by(self.lines.iter().copied(), G::filter);
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod unique;
|
|||
|
||||
use crate::app::{Filter, LineSet};
|
||||
use crate::logfile::LogLine;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::timegraph::{SparkLine, TimeGraph};
|
||||
use ratatui::layout::{Alignment, Constraint};
|
||||
use std::borrow::Cow;
|
||||
|
|
@ -11,49 +12,39 @@ use std::ops::RangeInclusive;
|
|||
use time::OffsetDateTime;
|
||||
pub use unique::*;
|
||||
|
||||
pub trait Grouping {
|
||||
const HEADER: &'static [(&'static str, Alignment)];
|
||||
|
||||
const WIDTHS: &'static [Constraint];
|
||||
}
|
||||
|
||||
pub trait FilterGrouping: Grouping {
|
||||
type Result: Grouping;
|
||||
type Identifier: Ord;
|
||||
|
||||
fn filter(line: &LogLine) -> Self::Identifier;
|
||||
pub struct GroupingUi {
|
||||
pub header: &'static [(&'static str, Alignment)],
|
||||
pub widths: &'static [Constraint],
|
||||
}
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub trait GroupingResult {
|
||||
type Grouping: Grouping;
|
||||
type Next: FilterGrouping;
|
||||
|
||||
pub trait GroupingResult<'a> {
|
||||
fn height(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn matches(&self, filter: &Filter) -> bool;
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<str>>;
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>>;
|
||||
}
|
||||
|
||||
pub struct LogGrouping<'logs, G> {
|
||||
#[derive(Clone)]
|
||||
pub struct LogGrouping<'logs> {
|
||||
pub name: Option<&'static str>,
|
||||
pub result: Option<G>,
|
||||
pub result: Option<Groupings>,
|
||||
pub count: usize,
|
||||
pub lines: LineSet<'logs>,
|
||||
pub by_identifier: OnceCell<Vec<LineSet<'logs>>>,
|
||||
}
|
||||
|
||||
impl<'logs, G: GroupingResult> LogGrouping<'logs, G> {
|
||||
pub fn new(result: G, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||
impl<'logs> LogGrouping<'logs> {
|
||||
pub fn new(result: impl Into<Groupings>, lines: Vec<&'logs LogLine<'logs>>) -> Self {
|
||||
let count = lines.len();
|
||||
let lines = LineSet::new(lines);
|
||||
|
||||
LogGrouping {
|
||||
name: None,
|
||||
result: Some(result),
|
||||
result: Some(result.into()),
|
||||
count,
|
||||
lines,
|
||||
by_identifier: OnceCell::new(),
|
||||
|
|
@ -122,3 +113,28 @@ where
|
|||
list.reverse();
|
||||
list
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum Groupings {
|
||||
Match(MatchResult),
|
||||
}
|
||||
|
||||
impl From<MatchResult> for Groupings {
|
||||
fn from(value: MatchResult) -> Self {
|
||||
Groupings::Match(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GroupingResult<'a> for Groupings {
|
||||
fn matches(&self, filter: &Filter) -> bool {
|
||||
match self {
|
||||
Groupings::Match(r) => r.matches(filter),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
match self {
|
||||
Groupings::Match(r) => r.render(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,29 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{FilterGrouping, Grouping, GroupingResult};
|
||||
use crate::grouping::{GroupingResult, GroupingUi};
|
||||
use crate::logfile::LogLine;
|
||||
use ratatui::layout::{Alignment, Constraint};
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct UniqueLog;
|
||||
|
||||
impl Grouping for UniqueLog {
|
||||
const HEADER: &'static [(&'static str, Alignment)] = &[
|
||||
("Level", Alignment::Left),
|
||||
("App", Alignment::Left),
|
||||
("Message", Alignment::Left),
|
||||
];
|
||||
|
||||
const WIDTHS: &'static [Constraint] = &[
|
||||
Constraint::Min(10),
|
||||
Constraint::Min(20),
|
||||
Constraint::Percentage(100),
|
||||
];
|
||||
}
|
||||
|
||||
impl FilterGrouping for UniqueLog {
|
||||
type Result = UniqueLog;
|
||||
type Identifier = u64;
|
||||
|
||||
fn filter(line: &LogLine) -> Self::Identifier {
|
||||
line.identity()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UniqueGrouping<'a> {
|
||||
pub line: &'a LogLine<'a>,
|
||||
}
|
||||
|
||||
impl<'a> GroupingResult for UniqueGrouping<'a> {
|
||||
type Grouping = UniqueLog;
|
||||
type Next = UniqueLog;
|
||||
pub const UNIQUE_GROUPING_UI: GroupingUi = GroupingUi {
|
||||
header: &[
|
||||
("Level", Alignment::Left),
|
||||
("App", Alignment::Left),
|
||||
("Message", Alignment::Left),
|
||||
],
|
||||
widths: &[
|
||||
Constraint::Min(10),
|
||||
Constraint::Min(20),
|
||||
Constraint::Percentage(100),
|
||||
],
|
||||
};
|
||||
|
||||
impl<'a> GroupingResult<'a> for UniqueGrouping<'a> {
|
||||
fn matches(&self, filter: &Filter) -> bool {
|
||||
if filter.is_empty() {
|
||||
return true;
|
||||
|
|
@ -46,7 +33,7 @@ impl<'a> GroupingResult for UniqueGrouping<'a> {
|
|||
.all(|filter_part| filter_part.is_match(&self.line.message))
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<str>> {
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
[
|
||||
Cow::from(self.line.level.as_str()),
|
||||
Cow::from(self.line.app.as_ref()),
|
||||
|
|
|
|||
|
|
@ -152,8 +152,8 @@ fn main() -> MainResult {
|
|||
matched_lines.sort_by_key(|(_, lines)| lines.len());
|
||||
matched_lines.reverse();
|
||||
|
||||
let all = LogGrouping::<MatchResult>::named("All", parsed_log.find_lines(|_| true).collect());
|
||||
let unmatched = LogGrouping::<MatchResult>::named("Unmatched", unmatched_lines);
|
||||
let all = LogGrouping::named("All", parsed_log.find_lines(|_| true).collect());
|
||||
let unmatched = LogGrouping::named("Unmatched", unmatched_lines);
|
||||
|
||||
let mut matches: Vec<_> = matched_lines
|
||||
.into_par_iter()
|
||||
|
|
@ -169,7 +169,6 @@ fn main() -> MainResult {
|
|||
|
||||
let app = App {
|
||||
lines: &parsed_log,
|
||||
matches: &matches,
|
||||
log_file: &log_file,
|
||||
unmatched_count,
|
||||
};
|
||||
|
|
@ -180,7 +179,7 @@ fn main() -> MainResult {
|
|||
|
||||
drop(progress);
|
||||
|
||||
run_ui(app)?;
|
||||
run_ui(app, matches)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{Grouping, GroupingResult, UniqueLog};
|
||||
use crate::grouping::{GroupingResult, GroupingUi};
|
||||
use crate::logfile::logline::{Exception, LogLine};
|
||||
use crate::logfile::LineNumber;
|
||||
use itertools::Either;
|
||||
|
|
@ -211,24 +211,20 @@ impl From<Vec<LoggingStatementWithPathPrefix>> for MatchResult {
|
|||
}
|
||||
}
|
||||
|
||||
impl Grouping for LoggingStatementWithPathPrefix {
|
||||
const HEADER: &'static [(&'static str, Alignment)] = &[
|
||||
pub const MATCH_GROUPING_UI: GroupingUi = GroupingUi {
|
||||
header: &[
|
||||
("Statement", Alignment::Left),
|
||||
("File", Alignment::Left),
|
||||
("Line", Alignment::Right),
|
||||
];
|
||||
|
||||
const WIDTHS: &'static [Constraint] = &[
|
||||
],
|
||||
widths: &[
|
||||
Constraint::Percentage(70),
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Min(6),
|
||||
];
|
||||
}
|
||||
|
||||
impl GroupingResult for MatchResult {
|
||||
type Grouping = LoggingStatementWithPathPrefix;
|
||||
type Next = UniqueLog;
|
||||
],
|
||||
};
|
||||
|
||||
impl<'a> GroupingResult<'a> for MatchResult {
|
||||
fn height(&self) -> usize {
|
||||
self.count()
|
||||
}
|
||||
|
|
@ -256,7 +252,7 @@ impl GroupingResult for MatchResult {
|
|||
})
|
||||
}
|
||||
|
||||
fn render(&self) -> impl Iterator<Item = Cow<str>> {
|
||||
fn render(&self) -> impl Iterator<Item = Cow<'a, str>> {
|
||||
let mut message = String::new();
|
||||
let mut paths = String::new();
|
||||
let mut lines = String::new();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use hdrhistogram::Histogram;
|
|||
use ratatui::text::Text;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::max;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -60,10 +61,18 @@ impl TimeGraph {
|
|||
}
|
||||
|
||||
// the biggest sparkline char is 3 bytes
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct SparkLine {
|
||||
bytes: [u8; 10 * 3],
|
||||
}
|
||||
|
||||
impl SparkLine {
|
||||
pub fn as_str(&self) -> &str {
|
||||
// SAFETY: we only put bytes into the buffer from encode_utf8
|
||||
unsafe { str::from_utf8_unchecked(&self.bytes).trim_end_matches(char::from(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[char; 10]> for SparkLine {
|
||||
fn from(value: [char; 10]) -> Self {
|
||||
let mut buff = [0; 10 * 3];
|
||||
|
|
@ -78,17 +87,19 @@ impl From<[char; 10]> for SparkLine {
|
|||
|
||||
impl<'a> From<&'a SparkLine> for Text<'a> {
|
||||
fn from(value: &'a SparkLine) -> Self {
|
||||
// SAFETY: we only put bytes into the buffer from encode_utf8
|
||||
let str = unsafe { str::from_utf8_unchecked(&value.bytes).trim_end_matches(char::from(0)) };
|
||||
Text::raw(str)
|
||||
Text::raw(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a SparkLine> for Cow<'a, str> {
|
||||
fn from(value: &'a SparkLine) -> Self {
|
||||
// SAFETY: we only put bytes into the buffer from encode_utf8
|
||||
let str = unsafe { str::from_utf8_unchecked(&value.bytes).trim_end_matches(char::from(0)) };
|
||||
str.into()
|
||||
value.as_str().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SparkLine {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crate::app::Filter;
|
||||
use crate::grouping::{Grouping, GroupingResult, LogGrouping};
|
||||
use crate::grouping::{GroupingResult, GroupingUi, LogGrouping};
|
||||
use crate::ui::style::TABLE_HEADER_STYLE;
|
||||
use crate::ui::table::ScrollbarTable;
|
||||
use ratatui::prelude::*;
|
||||
|
|
@ -8,12 +8,14 @@ use std::borrow::Cow;
|
|||
use std::ops::RangeInclusive;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub fn grouping_list<'a, G: GroupingResult>(
|
||||
items: &'a [LogGrouping<'a, G>],
|
||||
pub fn grouping_list<'a>(
|
||||
items: &[LogGrouping<'a>],
|
||||
ui: &GroupingUi,
|
||||
time_range: RangeInclusive<OffsetDateTime>,
|
||||
filter: &Filter,
|
||||
) -> ScrollbarTable<'a> {
|
||||
let header = G::Grouping::HEADER
|
||||
let header = ui
|
||||
.header
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([("Time", Alignment::Left), ("Count", Alignment::Left)]);
|
||||
|
|
@ -25,12 +27,15 @@ pub fn grouping_list<'a, G: GroupingResult>(
|
|||
.style(TABLE_HEADER_STYLE)
|
||||
.height(1);
|
||||
|
||||
let items: Vec<Row<'a>> = items
|
||||
.iter()
|
||||
.filter(|result| result.matches(filter))
|
||||
.map(|result| grouped_row(result, time_range.clone()))
|
||||
.collect();
|
||||
|
||||
ScrollbarTable::new(
|
||||
items
|
||||
.iter()
|
||||
.filter(|result| result.matches(filter))
|
||||
.map(|result| grouped_row(result, time_range.clone())),
|
||||
G::Grouping::WIDTHS
|
||||
items,
|
||||
ui.widths
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([Constraint::Length(10), Constraint::Min(10)]),
|
||||
|
|
@ -38,14 +43,14 @@ pub fn grouping_list<'a, G: GroupingResult>(
|
|||
.header(header)
|
||||
}
|
||||
|
||||
fn grouped_row<'a, G: GroupingResult>(
|
||||
grouping: &'a LogGrouping<'a, G>,
|
||||
fn grouped_row<'a>(
|
||||
grouping: &LogGrouping<'a>,
|
||||
time_range: RangeInclusive<OffsetDateTime>,
|
||||
) -> Row<'a> {
|
||||
if let Some(match_result) = &grouping.result {
|
||||
let grouping_columns = match_result.render();
|
||||
let columns = grouping_columns.chain([
|
||||
Cow::from(grouping.sparkline(time_range)),
|
||||
Cow::from(grouping.sparkline(time_range).to_string()),
|
||||
Cow::from(grouping.count().to_string()),
|
||||
]);
|
||||
Row::new(columns).height(match_result.height() as u16)
|
||||
|
|
@ -54,7 +59,7 @@ fn grouped_row<'a, G: GroupingResult>(
|
|||
Text::from(grouping.name.unwrap_or_default()),
|
||||
Text::from(""),
|
||||
Text::from(""),
|
||||
Text::from(grouping.sparkline(time_range)),
|
||||
Text::from(grouping.sparkline(time_range).to_string()),
|
||||
Text::from(grouping.count().to_string()),
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use crate::app::App;
|
||||
use crate::ui::find_hit_row;
|
||||
use crate::ui::state::{Mode, UiPage, UiState};
|
||||
use ratatui::crossterm::event;
|
||||
|
|
@ -30,7 +29,7 @@ pub enum PopMode {
|
|||
Word,
|
||||
}
|
||||
|
||||
pub fn handle_events(page: UiPage, ui_state: &UiState, app: &App) -> io::Result<Option<UiEvent>> {
|
||||
pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEvent>> {
|
||||
if event::poll(Duration::from_millis(50))? {
|
||||
match event::read()? {
|
||||
Event::Key(key) if key.kind == event::KeyEventKind::Press => {
|
||||
|
|
@ -77,7 +76,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState, app: &App) -> io::Result<
|
|||
MouseEventKind::ScrollUp => Some(UiEvent::Scroll(-1)),
|
||||
MouseEventKind::ScrollDown => Some(UiEvent::Scroll(1)),
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
find_hit_row(mouse.row, ui_state, app).map(UiEvent::Enter)
|
||||
find_hit_row(mouse.row, ui_state).map(UiEvent::Enter)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::app::App;
|
||||
use crate::error::UiError;
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::grouping::LogGrouping;
|
||||
use crate::ui::by_identifier::logs_by_identifier;
|
||||
use crate::ui::error_list::error_list;
|
||||
use crate::ui::footer::footer;
|
||||
|
|
@ -38,12 +38,12 @@ mod state;
|
|||
pub mod style;
|
||||
mod table;
|
||||
|
||||
pub fn run_ui<'a>(app: App<'a>) -> Result<(), UiError> {
|
||||
pub fn run_ui<'a>(app: App<'a>, matches: Vec<LogGrouping<'a>>) -> Result<(), UiError> {
|
||||
init_panic_hook();
|
||||
let mut terminal = init_tui()?;
|
||||
terminal.clear().ok();
|
||||
|
||||
let mut ui_state: UiState = UiState::new(&app);
|
||||
let mut ui_state: UiState = UiState::new(matches);
|
||||
let mut update = true;
|
||||
|
||||
while !matches!(ui_state, UiState::Quit) {
|
||||
|
|
@ -51,7 +51,7 @@ pub fn run_ui<'a>(app: App<'a>) -> Result<(), UiError> {
|
|||
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
|
||||
}
|
||||
update = false;
|
||||
if let Some(event) = handle_events(ui_state.page(), &ui_state, &app)? {
|
||||
if let Some(event) = handle_events(ui_state.page(), &ui_state)? {
|
||||
(update, ui_state) = ui_state.process(event, &app);
|
||||
}
|
||||
}
|
||||
|
|
@ -84,9 +84,9 @@ pub fn restore_tui() -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn find_hit_row(row: u16, ui_state: &UiState, app: &App) -> Option<usize> {
|
||||
fn find_hit_row(row: u16, ui_state: &UiState) -> Option<usize> {
|
||||
if let Some(table_row) = row.checked_sub(ui_state.content_offset()) {
|
||||
let selected = ui_state.index_for_row(table_row as usize, app);
|
||||
let selected = ui_state.index_for_row(table_row as usize);
|
||||
if selected < ui_state.row_count() {
|
||||
Some(selected)
|
||||
} else {
|
||||
|
|
@ -112,16 +112,18 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
|||
match state {
|
||||
UiState::Quit => {}
|
||||
UiState::GroupList(GroupListState {
|
||||
items,
|
||||
table_state,
|
||||
filter,
|
||||
ui,
|
||||
..
|
||||
}) => {
|
||||
let selected = table_state.selected();
|
||||
let histogram = app.matches[selected].histogram(app.time_range());
|
||||
let histogram = items[selected].histogram(app.time_range());
|
||||
|
||||
frame.render_widget(UiHistogram::new(histogram), layout[0]);
|
||||
frame.render_stateful_widget(
|
||||
grouping_list::<MatchResult>(app.matches, app.time_range(), filter),
|
||||
grouping_list(items, ui, app.time_range(), filter),
|
||||
layout[1],
|
||||
table_state,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::app::{App, Filter, EMPTY_FILTER};
|
||||
use crate::error::ParseError;
|
||||
use crate::grouping::LogGrouping;
|
||||
use crate::grouping::{GroupingUi, LogGrouping};
|
||||
use crate::logfile::logline::{FullLogLine, LogLine};
|
||||
use crate::matcher::MatchResult;
|
||||
use crate::matcher::MATCH_GROUPING_UI;
|
||||
use crate::ui::footer::FooterParams;
|
||||
use crate::ui::input::{PopMode, UiEvent};
|
||||
use crate::ui::table::ScrollbarTableState;
|
||||
|
|
@ -10,13 +10,12 @@ use crate::ui::UI_HEADER_SIZE;
|
|||
use crate::{copy_osc, parse_line_full};
|
||||
use derive_more::From;
|
||||
use ratatui::widgets::TableState;
|
||||
use std::borrow::Cow;
|
||||
use std::iter::once;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, From, PartialEq)]
|
||||
#[derive(From, PartialEq)]
|
||||
pub enum UiState<'a> {
|
||||
GroupList(GroupListState),
|
||||
GroupList(GroupListState<'a>),
|
||||
Group(GroupState<'a>),
|
||||
ByIdentifier(LogsByIdentifierState<'a>),
|
||||
Log(LogState<'a>),
|
||||
|
|
@ -31,27 +30,30 @@ pub enum Mode {
|
|||
FilterInput,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GroupListState {
|
||||
pub struct GroupListState<'a> {
|
||||
pub items: Vec<LogGrouping<'a>>,
|
||||
pub ui: GroupingUi,
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub filter: Filter,
|
||||
mode: Mode,
|
||||
}
|
||||
|
||||
impl GroupListState {
|
||||
impl<'a> GroupListState<'a> {
|
||||
fn selected(&self) -> usize {
|
||||
self.table_state.selected()
|
||||
}
|
||||
|
||||
fn enter<'a>(self, selected: usize, app: &App<'a>) -> UiState<'a> {
|
||||
fn enter(self, selected: usize) -> UiState<'a> {
|
||||
// todo remove clones?
|
||||
let result = if self.filter.is_empty() {
|
||||
&app.matches[selected - 1]
|
||||
self.items[selected].clone()
|
||||
} else {
|
||||
app.matches
|
||||
self.items
|
||||
.iter()
|
||||
.filter(|log_match| log_match.matches(&self.filter))
|
||||
.nth(selected - 1)
|
||||
.unwrap_or(app.matches.last().unwrap())
|
||||
.nth(selected)
|
||||
.unwrap_or(self.items.last().unwrap())
|
||||
.clone()
|
||||
};
|
||||
|
||||
let table_state = ScrollbarTableState::new(result.by_identifier().len() + 1);
|
||||
|
|
@ -65,15 +67,14 @@ impl GroupListState {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq for GroupListState {
|
||||
impl PartialEq for GroupListState<'_> {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GroupState<'a> {
|
||||
pub result: &'a LogGrouping<'a, MatchResult>,
|
||||
pub result: LogGrouping<'a>,
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub previous: Box<UiState<'a>>,
|
||||
pub filter: Filter,
|
||||
|
|
@ -89,10 +90,12 @@ impl<'a> GroupState<'a> {
|
|||
let mut table_state = TableState::default();
|
||||
table_state.select(Some(0));
|
||||
|
||||
// todo: remove clones
|
||||
|
||||
let selected_line = if selected == 0 {
|
||||
&self.result.lines
|
||||
self.result.lines.clone()
|
||||
} else if self.filter.is_empty() {
|
||||
&self.result.by_identifier()[selected - 1]
|
||||
self.result.by_identifier()[selected - 1].clone()
|
||||
} else {
|
||||
self.result
|
||||
.by_identifier()
|
||||
|
|
@ -100,6 +103,7 @@ impl<'a> GroupState<'a> {
|
|||
.filter(|grouped| grouped.matches(&self.filter))
|
||||
.nth(selected - 1)
|
||||
.expect("filtered select out of bounds")
|
||||
.clone()
|
||||
};
|
||||
let lines = selected_line.lines.as_slice();
|
||||
let table_state = ScrollbarTableState::new(lines.len());
|
||||
|
|
@ -126,9 +130,8 @@ pub enum GroupedLogGrouping {
|
|||
Request,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LogsByIdentifierState<'a> {
|
||||
pub lines: Cow<'a, [&'a LogLine<'a>]>,
|
||||
pub lines: Vec<&'a LogLine<'a>>,
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub previous: Box<UiState<'a>>,
|
||||
pub filter: Filter,
|
||||
|
|
@ -190,7 +193,7 @@ impl<'a> LogsByIdentifierState<'a> {
|
|||
|
||||
let table_state = ScrollbarTableState::new(lines.len());
|
||||
UiState::ByIdentifier(LogsByIdentifierState {
|
||||
lines: lines.into(),
|
||||
lines,
|
||||
mode: Mode::Normal,
|
||||
filter: Filter::default(),
|
||||
table_state,
|
||||
|
|
@ -206,7 +209,6 @@ impl PartialEq for LogsByIdentifierState<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ErrorLinesState<'a> {
|
||||
pub table_state: ScrollbarTableState,
|
||||
pub previous: Box<UiState<'a>>,
|
||||
|
|
@ -218,7 +220,6 @@ impl PartialEq for ErrorLinesState<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LogState<'a> {
|
||||
pub log: &'a LogLine<'a>,
|
||||
pub full_line: Box<FullLogLine>,
|
||||
|
|
@ -232,7 +233,7 @@ impl<'a> LogState<'a> {
|
|||
|
||||
let table_state = ScrollbarTableState::new(lines.len());
|
||||
UiState::ByIdentifier(LogsByIdentifierState {
|
||||
lines: lines.into(),
|
||||
lines,
|
||||
mode: Mode::Normal,
|
||||
filter: Filter::default(),
|
||||
table_state,
|
||||
|
|
@ -249,11 +250,14 @@ impl PartialEq for LogState<'_> {
|
|||
}
|
||||
|
||||
impl<'a> UiState<'a> {
|
||||
pub fn new(app: &App) -> Self {
|
||||
pub fn new(matches: Vec<LogGrouping<'a>>) -> Self {
|
||||
let mut table_state = TableState::default();
|
||||
table_state.select(Some(0));
|
||||
let lines = matches.len();
|
||||
UiState::GroupList(GroupListState {
|
||||
table_state: ScrollbarTableState::new(app.match_lines()),
|
||||
items: matches,
|
||||
ui: MATCH_GROUPING_UI,
|
||||
table_state: ScrollbarTableState::new(lines),
|
||||
filter: Filter::default(),
|
||||
mode: Mode::Normal,
|
||||
})
|
||||
|
|
@ -344,12 +348,11 @@ impl<'a> UiState<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn index_for_row(&self, row: usize, app: &App) -> usize {
|
||||
pub fn index_for_row(&self, row: usize) -> usize {
|
||||
match self {
|
||||
UiState::GroupList(GroupListState { filter, .. }) => {
|
||||
UiState::GroupList(GroupListState { filter, items, .. }) => {
|
||||
let mut total_height = 0;
|
||||
let match_row_counts = app
|
||||
.matches
|
||||
let match_row_counts = items
|
||||
.iter()
|
||||
.filter(|m| m.matches(filter))
|
||||
.map(|m| m.row_count());
|
||||
|
|
@ -365,9 +368,9 @@ impl<'a> UiState<'a> {
|
|||
total_height += row_count;
|
||||
}
|
||||
if total_height > row {
|
||||
app.matches.len() + 1
|
||||
items.len() + 1
|
||||
} else {
|
||||
app.matches.len() + 2
|
||||
items.len() + 2
|
||||
}
|
||||
}
|
||||
_ => row + self.scroll_offset(),
|
||||
|
|
@ -423,11 +426,9 @@ impl<'a> UiState<'a> {
|
|||
}
|
||||
(UiState::GroupList(state), UiEvent::Select) => {
|
||||
let selected = state.selected();
|
||||
(true, state.enter(selected, app))
|
||||
}
|
||||
(UiState::GroupList(state), UiEvent::Enter(selected)) => {
|
||||
(true, state.enter(selected, app))
|
||||
(true, state.enter(selected))
|
||||
}
|
||||
(UiState::GroupList(state), UiEvent::Enter(selected)) => (true, state.enter(selected)),
|
||||
(UiState::GroupList(state), UiEvent::Errors) => {
|
||||
let table_state = ScrollbarTableState::new(app.error_count());
|
||||
(
|
||||
|
|
@ -553,7 +554,7 @@ pub enum UiPage {
|
|||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(PartialEq)]
|
||||
pub struct ErrorState<'a> {
|
||||
pub error: Arc<ParseError>,
|
||||
pub previous: Box<UiState<'a>>,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use ratatui::widgets::{
|
|||
|
||||
pub struct ScrollbarTable<'a> {
|
||||
table: Table<'a>,
|
||||
scrollbar: Scrollbar<'a>,
|
||||
scrollbar: Scrollbar<'static>,
|
||||
}
|
||||
|
||||
impl<'a> ScrollbarTable<'a> {
|
||||
|
|
@ -19,7 +19,6 @@ impl<'a> ScrollbarTable<'a> {
|
|||
C: IntoIterator,
|
||||
C::Item: Into<Constraint>,
|
||||
{
|
||||
let rows: Vec<_> = rows.into_iter().collect();
|
||||
ScrollbarTable {
|
||||
table: Table::new(rows, widths)
|
||||
.block(Block::new().borders(Borders::RIGHT))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue