blob: c493a7387d6836f4cae8a934a6fe3c22e170a29f [file] [log] [blame]
use std::time::SystemTime;
use tui::{
buffer::Buffer,
layout::Rect,
style::{Color, Modifier, Style},
text::Span,
widgets::{Block, Borders, Widget},
};
use unicode_width::UnicodeWidthStr;
use crate::{
messages::{Message, MessageLevel},
render::tui::utils::{block_width, draw_text_with_ellipsis_nowrap, rect, sanitize_offset, VERTICAL_LINE},
time::{format_time_for_messages, DATE_TIME_HMS},
};
pub fn pane(messages: &[Message], bound: Rect, overflow_bound: Rect, offset: &mut u16, buf: &mut Buffer) {
let bold = Style::default().add_modifier(Modifier::BOLD);
let block = Block::default()
.title(Span::styled("Messages", bold))
.borders(Borders::TOP);
let inner_bound = block.inner(bound);
block.render(bound, buf);
let help_text = " ⨯ = `| ▢ = ~ ";
draw_text_with_ellipsis_nowrap(rect::snap_to_right(bound, block_width(help_text)), buf, help_text, bold);
let bound = inner_bound;
*offset = sanitize_offset(*offset, messages.len(), bound.height);
let max_origin_width = messages
.iter()
.rev()
.skip(*offset as usize)
.take(bound.height as usize)
.fold(0, |state, message| state.max(block_width(&message.origin)));
for (
line,
Message {
time,
message,
level,
origin,
},
) in messages
.iter()
.rev()
.skip(*offset as usize)
.take(bound.height as usize)
.enumerate()
{
let line_bound = rect::line_bound(bound, line);
let (time_bound, level_bound, origin_bound, message_bound) = compute_bounds(line_bound, max_origin_width);
if let Some(time_bound) = time_bound {
draw_text_with_ellipsis_nowrap(time_bound, buf, format_time_column(time), None);
}
if let Some(level_bound) = level_bound {
draw_text_with_ellipsis_nowrap(
level_bound,
buf,
format_level_column(*level),
Some(level_to_style(*level)),
);
draw_text_with_ellipsis_nowrap(rect::offset_x(level_bound, LEVEL_TEXT_WIDTH), buf, VERTICAL_LINE, None);
}
if let Some(origin_bound) = origin_bound {
draw_text_with_ellipsis_nowrap(origin_bound, buf, origin, None);
draw_text_with_ellipsis_nowrap(rect::offset_x(origin_bound, max_origin_width), buf, "→", None);
}
draw_text_with_ellipsis_nowrap(message_bound, buf, message, None);
}
if (bound.height as usize) < messages.len().saturating_sub(*offset as usize)
|| (*offset).min(messages.len() as u16) > 0
{
let messages_below = messages
.len()
.saturating_sub(bound.height.saturating_add(*offset) as usize);
let messages_skipped = (*offset).min(messages.len() as u16);
draw_text_with_ellipsis_nowrap(
rect::offset_x(overflow_bound, 1),
buf,
format!("… {} skipped and {} more", messages_skipped, messages_below),
bold,
);
let help_text = " ⇊ = D|↓ = J|⇈ = U|↑ = K ┘";
draw_text_with_ellipsis_nowrap(
rect::snap_to_right(overflow_bound, block_width(help_text)),
buf,
help_text,
bold,
);
}
}
const LEVEL_TEXT_WIDTH: u16 = 4;
fn format_level_column(level: MessageLevel) -> &'static str {
use MessageLevel::*;
match level {
Info => "info",
Failure => "fail",
Success => "done",
}
}
fn level_to_style(level: MessageLevel) -> Style {
use MessageLevel::*;
Style::default()
.fg(Color::Black)
.add_modifier(Modifier::BOLD)
.bg(match level {
Info => Color::White,
Failure => Color::Red,
Success => Color::Green,
})
}
fn format_time_column(time: &SystemTime) -> String {
format!("{}{}", format_time_for_messages(*time), VERTICAL_LINE)
}
fn compute_bounds(line: Rect, max_origin_width: u16) -> (Option<Rect>, Option<Rect>, Option<Rect>, Rect) {
let vertical_line_width = VERTICAL_LINE.width() as u16;
let mythical_offset_we_should_not_need = 1;
let time_bound = Rect {
width: DATE_TIME_HMS as u16 + vertical_line_width,
..line
};
let mut cursor = time_bound.width + mythical_offset_we_should_not_need;
let level_bound = Rect {
x: cursor,
width: LEVEL_TEXT_WIDTH + vertical_line_width,
..line
};
cursor += level_bound.width;
let origin_bound = Rect {
x: cursor,
width: max_origin_width + vertical_line_width,
..line
};
cursor += origin_bound.width;
let message_bound = rect::intersect(rect::offset_x(line, cursor), line);
if message_bound.width < 30 {
return (None, None, None, line);
}
(Some(time_bound), Some(level_bound), Some(origin_bound), message_bound)
}