This commit is contained in:
Jana Dönszelmann 2026-02-25 13:06:25 +01:00
parent d8e445b5f7
commit 4a7817a239
No known key found for this signature in database
4 changed files with 82 additions and 8 deletions

View file

@ -1,20 +1,20 @@
use ratatui_themes::{Theme, ThemeName}; use ratatui_themes::{Theme, ThemeName};
use regex::bytes::Regex;
use std::{ use std::{
cell::RefCell,
fs::{self, DirEntry}, fs::{self, DirEntry},
io, io,
ops::ControlFlow, ops::ControlFlow,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::exit, process::exit,
rc::Rc, rc::Rc,
time::Instant,
}; };
use tui_widget_list::{ListBuilder, ListView}; use tui_widget_list::{ListBuilder, ListView};
use crate::tui::{ use crate::tui::{
filter::FilterKind, filter::FilterKind,
log_viewer::{InputState, InputTarget, LogViewer}, log_viewer::{InputState, InputTarget, LogViewer},
model::pretty_print_value, widgets::{hyperlink::Hyperlink, items::Items, last_error::LastError},
widgets::{hyperlink::Hyperlink, items::Items, line_text::Highlighted},
}; };
use crate::tui::{ use crate::tui::{
filter::{Filter, Matcher}, filter::{Filter, Matcher},
@ -27,7 +27,7 @@ use ratatui::{
buffer::Buffer, buffer::Buffer,
crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
layout::{Constraint, HorizontalAlignment, Layout, Rect}, layout::{Constraint, HorizontalAlignment, Layout, Rect},
style::Style, style::{self, Style},
text::Line, text::Line,
widgets::{ widgets::{
Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap, Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap,
@ -116,6 +116,8 @@ struct App {
compiler_root: Option<PathBuf>, compiler_root: Option<PathBuf>,
current_file: Option<LogfileReader>, current_file: Option<LogfileReader>,
theme: Theme, theme: Theme,
last_error: LastError,
} }
impl App { impl App {
@ -126,6 +128,7 @@ impl App {
logs_dir, logs_dir,
compiler_root, compiler_root,
theme, theme,
last_error: LastError::new(),
}; };
res.replace_tab(res.choose_file()); res.replace_tab(res.choose_file());
res res
@ -356,6 +359,8 @@ impl App {
fn run(mut self, mut terminal: DefaultTerminal) -> io::Result<()> { fn run(mut self, mut terminal: DefaultTerminal) -> io::Result<()> {
loop { loop {
self.last_error.check_expired();
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
@ -376,12 +381,14 @@ impl App {
let highlighted = Style::new().fg(palette.accent).bg(palette.selection); let highlighted = Style::new().fg(palette.accent).bg(palette.selection);
let border = Style::new().fg(palette.fg).bg(palette.bg); let border = Style::new().fg(palette.fg).bg(palette.bg);
let border_highlighted = Style::new().fg(palette.secondary).bg(palette.bg); let border_highlighted = Style::new().fg(palette.secondary).bg(palette.bg);
let error = Style::new().fg(palette.error).bg(palette.bg);
Styles { Styles {
default, default,
highlighted, highlighted,
border, border,
border_highlighted, border_highlighted,
error,
} }
} }
@ -400,11 +407,12 @@ impl App {
} }
} }
struct Styles { pub struct Styles {
default: Style, default: Style,
highlighted: Style, highlighted: Style,
border: Style, border: Style,
border_highlighted: Style, border_highlighted: Style,
error: Style,
} }
impl Widget for &mut App { impl Widget for &mut App {
@ -471,6 +479,8 @@ impl Widget for &mut App {
.style(styles.default) .style(styles.default)
.render(middle, buf); .render(middle, buf);
let [right, error] =
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(right);
Paragraph::new("").style(styles.default).render(right, buf); Paragraph::new("").style(styles.default).render(right, buf);
for tab in &mut self.tabs { for tab in &mut self.tabs {
@ -533,6 +543,7 @@ impl Widget for &mut App {
.get(idx) .get(idx)
.map(|(a, b)| (a.clone(), b.clone())) .map(|(a, b)| (a.clone(), b.clone()))
}), }),
self.last_error.clone(),
) )
.styled_ref(&styles) .styled_ref(&styles)
.render(main_area, buf); .render(main_area, buf);
@ -575,6 +586,8 @@ impl Widget for &mut App {
Paragraph::new(HELP_TEXT).render(popup_area, buf); Paragraph::new(HELP_TEXT).render(popup_area, buf);
} }
} }
self.last_error.clone().styled(&styles).render(error, buf);
} }
} }
} }

View file

@ -1,4 +1,4 @@
use std::rc::Rc; use std::{cell::OnceCell, rc::Rc};
use itertools::Itertools; use itertools::Itertools;
use ratatui::widgets::{List, ListItem, Widget}; use ratatui::widgets::{List, ListItem, Widget};
@ -8,6 +8,7 @@ use crate::tui::{
log_viewer::{FieldMatcher, InputState, InputTarget}, log_viewer::{FieldMatcher, InputState, InputTarget},
model::LogEntry, model::LogEntry,
widgets::{ widgets::{
last_error::LastError,
line_text::Highlighted, line_text::Highlighted,
styled::{IntoStyled, Styled}, styled::{IntoStyled, Styled},
}, },
@ -92,6 +93,7 @@ pub struct Items<'a> {
input_state: &'a InputState, input_state: &'a InputState,
selected_footer_field: Option<(String, String)>, selected_footer_field: Option<(String, String)>,
last_error: LastError,
} }
impl<'a> Items<'a> { impl<'a> Items<'a> {
@ -100,12 +102,14 @@ impl<'a> Items<'a> {
selected_offset: usize, selected_offset: usize,
input_state: &'a InputState, input_state: &'a InputState,
selected_footer_field: Option<(String, String)>, selected_footer_field: Option<(String, String)>,
last_error: LastError,
) -> Self { ) -> Self {
Self { Self {
items, items,
selected_offset, selected_offset,
input_state, input_state,
selected_footer_field, selected_footer_field,
last_error,
} }
} }
@ -120,6 +124,7 @@ impl Widget for Styled<'_, &Items<'_>> {
Self: Sized, Self: Sized,
{ {
let ts = TreeState::from_items(&self.items); let ts = TreeState::from_items(&self.items);
let regex_cache = OnceCell::new();
let list = List::new( let list = List::new(
self.inner self.inner
@ -161,8 +166,13 @@ impl Widget for Styled<'_, &Items<'_>> {
} }
} }
FieldMatcher::Regex(r) => { FieldMatcher::Regex(r) => {
if let Ok(regex) = Regex::new(r) if let Ok(regex) = regex_cache.get_or_init(|| {
&& let Some(start_offset) = line.message.find(msg) let regex = Regex::new(r);
if let Err(e) = &regex {
self.last_error.set(format!("{e}"));
}
regex
}) && let Some(start_offset) = line.message.find(msg)
&& let Some(m) = regex.find(msg) && let Some(m) = regex.find(msg)
{ {
let from = start_offset + m.start(); let from = start_offset + m.start();

View file

@ -0,0 +1,50 @@
use std::{cell::RefCell, rc::Rc, time::Instant};
use ratatui::widgets::{Paragraph, Widget};
use crate::tui::widgets::styled::Styled;
#[derive(Clone)]
pub struct LastError {
inner: Rc<RefCell<Option<(String, Instant)>>>,
}
impl LastError {
pub fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(None)),
}
}
pub fn set(&self, s: String) {
*self.inner.borrow_mut() = Some((s, Instant::now()));
}
pub fn check_expired(&self) {
let inner = self.inner.borrow();
if let Some((_, time)) = *inner {
if Instant::now().duration_since(time).as_secs_f64() > 1.0 {
drop(inner);
*self.inner.borrow_mut() = None;
}
}
}
}
impl Widget for Styled<'_, LastError> {
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
where
Self: Sized,
{
Paragraph::new(
self.inner
.inner
.borrow()
.as_ref()
.map(|i| i.0.replace("\n", ""))
.unwrap_or_default(),
)
.style(self.styles.error)
.render(area, buf);
}
}

View file

@ -1,4 +1,5 @@
pub mod hyperlink; pub mod hyperlink;
pub mod items; pub mod items;
pub mod last_error;
pub mod line_text; pub mod line_text;
pub mod styled; pub mod styled;