p2p_chat/ui/log_mode/
render.rs

1//! This module contains the rendering logic for the log UI mode.
2use super::super::UIState;
3use super::LogMode;
4use anyhow::Result;
5use crossterm::{
6    cursor, queue,
7    style::{Color, Print, ResetColor, SetForegroundColor},
8};
9use std::io::Write;
10use tracing::Level;
11
12impl LogMode {
13    /// Renders the log view to the terminal.
14    ///
15    /// This function displays filtered log entries, handling scrolling and
16    /// applying color coding based on log levels.
17    ///
18    /// # Arguments
19    ///
20    /// * `stdout` - A mutable reference to the output stream.
21    /// * `state` - The current UI state, containing log entries and scroll offsets.
22    /// * `area` - The (x, y, width, height) coordinates of the rendering area.
23    ///
24    /// # Errors
25    ///
26    /// This function returns an error if writing to the output stream fails.
27    pub fn render(
28        &self,
29        stdout: &mut impl Write,
30        state: &UIState,
31        area: (u16, u16, u16, u16),
32    ) -> Result<()> {
33        let (x, y, width, height) = area;
34
35        let filtered_logs = state.filtered_logs();
36        let total_logs = filtered_logs.len();
37        let visible_lines = height as usize;
38
39        // Calculate the starting index for displaying logs based on scroll offset.
40        let start_idx = if total_logs > visible_lines {
41            if state.log_scroll_offset >= total_logs {
42                0
43            } else {
44                total_logs.saturating_sub(visible_lines + state.log_scroll_offset)
45            }
46        } else {
47            0
48        };
49
50        let end_idx = (start_idx + visible_lines).min(total_logs);
51
52        // Iterate and render visible log entries.
53        for (line_idx, log_idx) in (start_idx..end_idx).enumerate() {
54            if let Some(log_entry) = filtered_logs.get(log_idx) {
55                queue!(stdout, cursor::MoveTo(x, y + line_idx as u16))?;
56
57                let timestamp = log_entry
58                    .timestamp
59                    .with_timezone(&chrono::Local)
60                    .format("%H:%M:%S%.3f");
61
62                let level_color = match log_entry.level {
63                    Level::ERROR => Color::Red,
64                    Level::WARN => Color::Yellow,
65                    Level::INFO => Color::Blue,
66                    Level::DEBUG => Color::White,
67                    Level::TRACE => Color::DarkGrey,
68                };
69
70                let log_line = format!(
71                    "{} {:5} [{}] {}",
72                    timestamp,
73                    format!("{:?}", log_entry.level),
74                    log_entry.module,
75                    log_entry.message
76                );
77
78                // Handle horizontal scrolling.
79                let scrolled_line = if state.horizontal_scroll_offset < log_line.chars().count() {
80                    log_line
81                        .chars()
82                        .skip(state.horizontal_scroll_offset)
83                        .collect::<String>()
84                } else {
85                    String::new()
86                };
87
88                // Truncate line if it exceeds terminal width.
89                let display_line = if scrolled_line.chars().count() > width as usize {
90                    let truncated: String =
91                        scrolled_line.chars().take(width as usize - 3).collect();
92                    format!("{}...", truncated)
93                } else {
94                    scrolled_line
95                };
96
97                queue!(
98                    stdout,
99                    SetForegroundColor(level_color),
100                    Print(display_line),
101                    ResetColor
102                )?;
103            }
104        }
105
106        // Render vertical scroll indicator.
107        if state.log_scroll_offset > 0 {
108            queue!(
109                stdout,
110                cursor::MoveTo(x + width - 15, y),
111                SetForegroundColor(Color::Yellow),
112                Print(format!("↑ +{} more logs", state.log_scroll_offset)),
113                ResetColor
114            )?;
115        }
116
117        // Render horizontal scroll indicator.
118        if state.horizontal_scroll_offset > 0 {
119            queue!(
120                stdout,
121                cursor::MoveTo(x, y),
122                SetForegroundColor(Color::Yellow),
123                Print(format!("← +{}", state.horizontal_scroll_offset)),
124                ResetColor
125            )?;
126        }
127
128        Ok(())
129    }
130}