use crate::app::state::{App, InputMode};
use crate::ui::theme::{StatusKind, Theme};
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect},
style::Modifier,
text::Line,
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
};
pub fn render_logs(f: &mut Frame, app: &App, area: Rect, theme: &Theme) {
let (list_area, search_area) =
if app.input_mode == InputMode::LogSearch || app.log_search_active {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(0), Constraint::Length(3)])
.split(area);
(chunks[0], Some(chunks[1]))
} else {
(area, None)
};
let block_style = if app.focus == crate::app::state::AppFocus::Logs || app.log_search_active {
theme.border_active_style()
} else {
theme.text_style()
};
let title = format!(
" System Logs [Level: {}] (Scroll: arrows | 1-5: Level) ",
app.current_log_level.to_uppercase()
);
let block = Block::default()
.borders(Borders::ALL)
.title(title)
.border_type(theme.border_type)
.border_style(block_style)
.style(theme.panel_style());
let _inner_area = block.inner(list_area);
let items: Vec<ListItem> = app
.logs
.iter()
.enumerate()
.map(|(i, line)| {
let is_match = app.log_search_results.contains(&i);
let mut style = theme.text_style();
if line.contains("ERROR") {
style = theme.status_style(StatusKind::Error);
} else if line.contains("WARN") {
style = theme.status_style(StatusKind::Warning);
} else if line.contains("INFO") {
style = theme.status_style(StatusKind::Info);
} else if line.contains("DEBUG") {
style = theme.accent_alt_style();
} else if line.contains("TRACE") {
style = theme.dim_style();
}
let is_current = !app.log_search_results.is_empty()
&& app.log_search_results.get(app.log_search_current) == Some(&i);
if is_current {
style = theme.selection_style();
} else if is_match {
style = style
.bg(theme.table_row_alt_bg)
.add_modifier(Modifier::BOLD);
}
let display_text = if app.logs_scroll_x == 0 {
line.as_str()
} else {
let start_byte = line
.char_indices()
.map(|(i, _)| i)
.nth(app.logs_scroll_x)
.unwrap_or(line.len());
&line[start_byte..]
};
ListItem::new(Line::from(display_text)).style(style)
})
.collect();
let mut state = ListState::default();
if app.logs_stick_to_bottom && !app.logs.is_empty() {
state.select(Some(app.logs.len() - 1));
} else if app.logs_scroll < app.logs.len() {
state.select(Some(app.logs_scroll));
} else if !app.logs.is_empty() {
state.select(Some(app.logs.len() - 1));
}
f.render_stateful_widget(
List::new(items)
.block(block)
.highlight_style(theme.selection_style()),
list_area,
&mut state,
);
if let Some(s_area) = search_area {
let title = if !app.log_search_results.is_empty() {
format!(" Search ({} matches) ", app.log_search_results.len())
} else {
" Search ".to_string()
};
let border_style = if app.input_mode == InputMode::LogSearch {
theme.accent_style()
} else {
theme.border_style()
};
let search_block = Block::default()
.borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
.title(title)
.border_type(theme.border_type)
.border_style(border_style);
let query_text = format!(
"{}{}",
app.log_search_query,
if app.input_mode == InputMode::LogSearch {
"█"
} else {
""
}
);
f.render_widget(
Paragraph::new(query_text)
.block(search_block)
.style(theme.text_style()),
s_area,
);
}
}