case insentive filtering

This commit is contained in:
Robin Appelman 2024-12-17 14:33:36 +01:00
commit 831ae1a9e8
7 changed files with 91 additions and 24 deletions

View file

@ -3,7 +3,9 @@ use crate::logline::LogLine;
use crate::matcher::MatchResult; use crate::matcher::MatchResult;
use crate::timegraph::TimeGraph; use crate::timegraph::TimeGraph;
use logsmash_data::{LoggingStatementWithPathPrefix, StatementList}; use logsmash_data::{LoggingStatementWithPathPrefix, StatementList};
use regex::{escape, Regex, RegexBuilder};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt::Display;
pub struct App<'a> { pub struct App<'a> {
pub lines: Vec<LogLine<'a>>, pub lines: Vec<LogLine<'a>>,
@ -73,12 +75,12 @@ impl LogMatch {
.filter_map(|index| app.log_statements.get(index)) .filter_map(|index| app.log_statements.get(index))
} }
pub fn matches(&self, app: &App, filter: &str) -> bool { pub fn matches(&self, app: &App, filter: &Filter) -> bool {
if filter.is_empty() { if filter.is_empty() {
return true; return true;
} }
self.statements(app) self.statements(app)
.any(|statement| statement.pattern.contains(filter)) .any(|statement| filter.matches(statement.pattern))
} }
} }
@ -130,11 +132,75 @@ impl GroupedLines {
self.lines.len() self.lines.len()
} }
pub fn matches(&self, app: &App, filter: &str) -> bool { pub fn matches(&self, app: &App, filter: &Filter) -> bool {
if filter.is_empty() { if filter.is_empty() {
return true; return true;
} }
let line = &app.lines[self.lines[0]]; let line = &app.lines[self.lines[0]];
line.message.contains(filter) filter.matches(&line.message)
}
}
#[derive(Default, Clone)]
pub struct Filter {
filter: String,
regex: Option<Regex>,
}
pub static EMPTY_FILTER: Filter = Filter {
filter: String::new(),
regex: None,
};
impl Filter {
fn build_regex(filter: &str) -> Option<Regex> {
if filter.is_empty() {
None
} else {
Some(
RegexBuilder::new(&escape(&filter))
.case_insensitive(true)
.build()
.unwrap(),
)
}
}
#[allow(dead_code)]
pub fn new(filter: String) -> Self {
let regex = Self::build_regex(&filter);
Filter { filter, regex }
}
pub fn matches(&self, string: &str) -> bool {
match &self.regex {
Some(regex) => regex.is_match(string),
None => true,
}
}
pub fn is_empty(&self) -> bool {
self.filter.is_empty()
}
pub fn push(&mut self, c: char) {
self.filter.push(c);
self.regex = Self::build_regex(&self.filter);
}
pub fn pop(&mut self) {
self.filter.pop();
self.regex = Self::build_regex(&self.filter);
}
pub fn clear(&mut self) {
self.filter.clear();
self.regex = None;
}
}
impl Display for Filter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.filter)
} }
} }

View file

@ -1,3 +1,4 @@
use crate::app::Filter;
use ahash::AHasher; use ahash::AHasher;
use logsmash_data::LogLevel; use logsmash_data::LogLevel;
use serde::Deserialize; use serde::Deserialize;
@ -126,12 +127,12 @@ impl<'a> LogLine<'a> {
} }
} }
pub fn matches(&self, filter: &str) -> bool { pub fn matches(&self, filter: &Filter) -> bool {
if filter.is_empty() { if filter.is_empty() {
return true; return true;
} }
// todo: reqid, more? // todo: reqid, more?
self.app.contains(filter) || self.message.contains(filter) filter.matches(&self.app) || filter.matches(&self.message)
} }
} }

View file

@ -1,4 +1,4 @@
use crate::app::App; use crate::app::{App, Filter};
use crate::ui::state::UiPage; use crate::ui::state::UiPage;
use ratatui::layout::Constraint; use ratatui::layout::Constraint;
use ratatui::prelude::Style; use ratatui::prelude::Style;
@ -8,7 +8,7 @@ use ratatui::widgets::{Row, Table};
pub enum FooterParams<'a> { pub enum FooterParams<'a> {
Normal { page: UiPage }, Normal { page: UiPage },
FilterInput { page: UiPage, filter: &'a str }, FilterInput { page: UiPage, filter: &'a Filter },
} }
pub fn footer<'a>(app: &App<'a>, params: FooterParams<'a>) -> Table<'a> { pub fn footer<'a>(app: &App<'a>, params: FooterParams<'a>) -> Table<'a> {

View file

@ -1,4 +1,4 @@
use crate::app::{App, LogMatch}; use crate::app::{App, Filter, LogMatch};
use crate::ui::style::TABLE_HEADER_STYLE; use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable; use crate::ui::table::ScrollbarTable;
use itertools::Either; use itertools::Either;
@ -7,7 +7,7 @@ use ratatui::widgets::{Cell, Row};
use std::fmt::Write; use std::fmt::Write;
use std::iter::{empty, once}; use std::iter::{empty, once};
pub fn match_list<'a>(app: &'a App<'a>, filter: &str) -> ScrollbarTable<'a> { pub fn match_list<'a>(app: &'a App<'a>, filter: &Filter) -> ScrollbarTable<'a> {
let header = [ let header = [
Text::from("Statement"), Text::from("Statement"),
Text::from("File"), Text::from("File"),

View file

@ -1,4 +1,4 @@
use crate::app::App; use crate::app::{App, Filter};
use crate::logline::{format_time, LogLine}; use crate::logline::{format_time, LogLine};
use crate::ui::style::TABLE_HEADER_STYLE; use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable; use crate::ui::table::ScrollbarTable;
@ -6,7 +6,7 @@ use ratatui::layout::{Alignment, Constraint};
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
pub fn raw_logs<'a>(app: &'a App<'a>, lines: &[usize], filter: &str) -> ScrollbarTable<'a> { pub fn raw_logs<'a>(app: &'a App<'a>, lines: &[usize], filter: &Filter) -> ScrollbarTable<'a> {
let lines = lines.iter().copied().map(|i| &app.lines[i]); let lines = lines.iter().copied().map(|i| &app.lines[i]);
let header = [ let header = [
Text::from("Level"), Text::from("Level"),

View file

@ -1,4 +1,4 @@
use crate::app::{App, GroupedLines, LogMatch}; use crate::app::{App, Filter, GroupedLines, LogMatch};
use crate::ui::style::TABLE_HEADER_STYLE; use crate::ui::style::TABLE_HEADER_STYLE;
use crate::ui::table::ScrollbarTable; use crate::ui::table::ScrollbarTable;
use ratatui::layout::Constraint; use ratatui::layout::Constraint;
@ -8,7 +8,7 @@ use ratatui::widgets::{Cell, Row};
pub fn grouped_lines<'a>( pub fn grouped_lines<'a>(
app: &'a App<'a>, app: &'a App<'a>,
log_match: &'a LogMatch, log_match: &'a LogMatch,
filter: &str, filter: &Filter,
) -> ScrollbarTable<'a> { ) -> ScrollbarTable<'a> {
let grouped = &log_match.grouped; let grouped = &log_match.grouped;
let header = [ let header = [

View file

@ -1,4 +1,4 @@
use crate::app::{App, LogMatch}; use crate::app::{App, Filter, LogMatch, EMPTY_FILTER};
use crate::logline::{FullLogLine, LogLine}; use crate::logline::{FullLogLine, LogLine};
use crate::ui::footer::FooterParams; use crate::ui::footer::FooterParams;
use crate::ui::table::ScrollbarTableState; use crate::ui::table::ScrollbarTableState;
@ -28,7 +28,7 @@ pub enum Mode {
pub struct MatchListState<'a> { pub struct MatchListState<'a> {
app: &'a App<'a>, app: &'a App<'a>,
pub table_state: ScrollbarTableState, pub table_state: ScrollbarTableState,
pub filter: String, pub filter: Filter,
mode: Mode, mode: Mode,
} }
@ -58,7 +58,7 @@ impl<'a> MatchListState<'a> {
result, result,
table_state, table_state,
previous: Box::new(self.into()), previous: Box::new(self.into()),
filter: String::new(), filter: Filter::default(),
mode: Mode::Normal, mode: Mode::Normal,
}) })
} }
@ -75,7 +75,7 @@ pub struct MatchState<'a> {
pub result: &'a LogMatch, pub result: &'a LogMatch,
pub table_state: ScrollbarTableState, pub table_state: ScrollbarTableState,
pub previous: Box<UiState<'a>>, pub previous: Box<UiState<'a>>,
pub filter: String, pub filter: Filter,
mode: Mode, mode: Mode,
} }
@ -104,7 +104,7 @@ impl<'a> MatchState<'a> {
lines, lines,
table_state, table_state,
previous: Box::new(self.into()), previous: Box::new(self.into()),
filter: String::new(), filter: Filter::default(),
mode: Mode::Normal, mode: Mode::Normal,
}) })
} }
@ -121,7 +121,7 @@ pub struct LogsState<'a> {
pub lines: &'a [usize], pub lines: &'a [usize],
pub table_state: ScrollbarTableState, pub table_state: ScrollbarTableState,
pub previous: Box<UiState<'a>>, pub previous: Box<UiState<'a>>,
pub filter: String, pub filter: Filter,
mode: Mode, mode: Mode,
} }
@ -200,7 +200,7 @@ impl<'a> UiState<'a> {
UiState::MatchList(MatchListState { UiState::MatchList(MatchListState {
app, app,
table_state: ScrollbarTableState::new(app.match_lines()), table_state: ScrollbarTableState::new(app.match_lines()),
filter: String::new(), filter: Filter::default(),
mode: Mode::Normal, mode: Mode::Normal,
}) })
} }
@ -233,7 +233,7 @@ impl<'a> UiState<'a> {
} }
} }
pub fn filter(&self) -> Option<&str> { pub fn filter(&self) -> Option<&Filter> {
match self { match self {
UiState::MatchList(state) => Some(&state.filter), UiState::MatchList(state) => Some(&state.filter),
UiState::Match(state) => Some(&state.filter), UiState::Match(state) => Some(&state.filter),
@ -242,7 +242,7 @@ impl<'a> UiState<'a> {
} }
} }
pub fn filter_mut(&mut self) -> Option<&mut String> { pub fn filter_mut(&mut self) -> Option<&mut Filter> {
match self { match self {
UiState::MatchList(state) => Some(&mut state.filter), UiState::MatchList(state) => Some(&mut state.filter),
UiState::Match(state) => Some(&mut state.filter), UiState::Match(state) => Some(&mut state.filter),
@ -468,7 +468,7 @@ impl<'a> UiState<'a> {
match self.mode() { match self.mode() {
Mode::Normal => FooterParams::Normal { page: self.page() }, Mode::Normal => FooterParams::Normal { page: self.page() },
Mode::FilterInput => FooterParams::FilterInput { Mode::FilterInput => FooterParams::FilterInput {
filter: self.filter().unwrap_or_default(), filter: self.filter().unwrap_or(&EMPTY_FILTER),
page: self.page(), page: self.page(),
}, },
} }