p2p_chat/ui/state/
logs.rs

1//! This module contains log-related functionalities for the `UIState`.
2use crate::ui::{log_entry::LogEntry, mode::UIMode};
3
4use super::UIState;
5
6impl UIState {
7    /// Adds a batch of new log entries to the UI state.
8    ///
9    /// This function appends new log entries to the buffer, enforcing `max_log_entries`.
10    /// If the UI is in log mode and at the bottom of the scroll, it resets the
11    /// scroll offset. Otherwise, it adjusts the scroll offset to keep new
12    /// messages visible if not explicitly scrolled up.
13    ///
14    /// # Arguments
15    ///
16    /// * `entries` - A `Vec` of `LogEntry` to add.
17    pub fn add_log_batch(&mut self, entries: Vec<LogEntry>) {
18        let new_entries_count = entries.len();
19
20        for entry in entries {
21            if self.logs.len() >= self.max_log_entries {
22                self.logs.pop_front();
23            }
24            self.logs.push_back(entry);
25        }
26
27        if matches!(self.mode, UIMode::Logs { .. }) {
28            if self.is_at_bottom_log {
29                self.log_scroll_offset = 0;
30            } else {
31                self.log_scroll_offset = self.log_scroll_offset.saturating_add(new_entries_count);
32                self.update_log_scroll_state(self.terminal_size.1 as usize);
33            }
34        }
35    }
36
37    /// Triggers a refresh of the log display.
38    ///
39    /// This function resets the log scroll offset and marks the view as being
40    /// at the bottom, useful when filter settings change.
41    pub fn refresh_logs(&mut self) {
42        if matches!(self.mode, UIMode::Logs { .. }) {
43            self.log_scroll_offset = 0;
44            self.is_at_bottom_log = true;
45        }
46    }
47
48    /// Updates the log scroll state based on the current terminal height.
49    ///
50    /// This ensures that the `log_scroll_offset` remains within valid bounds and
51    /// updates `is_at_bottom_log`.
52    ///
53    /// # Arguments
54    ///
55    /// * `terminal_height` - The current height of the terminal in lines.
56    pub fn update_log_scroll_state(&mut self, terminal_height: usize) {
57        let filtered_logs = self.filtered_logs();
58        let total_logs = filtered_logs.len();
59        let visible_lines = terminal_height.saturating_sub(2); // Account for input and status lines
60        let max_scroll = if total_logs > visible_lines {
61            total_logs.saturating_sub(visible_lines)
62        } else {
63            0
64        };
65
66        self.log_scroll_offset = self.log_scroll_offset.min(max_scroll);
67        self.is_at_bottom_log = self.log_scroll_offset == 0;
68    }
69
70    /// Scrolls the log view to the bottom.
71    pub fn jump_to_bottom_log(&mut self) {
72        self.log_scroll_offset = 0;
73        self.is_at_bottom_log = true;
74    }
75
76    /// Returns a vector of log entries filtered by the current `UIMode::Logs` settings.
77    ///
78    /// Logs can be filtered by minimum `Level` and by a text filter string,
79    /// which can include exclusions prefixed with `-`.
80    pub fn filtered_logs(&self) -> Vec<&LogEntry> {
81        match &self.mode {
82            UIMode::Logs { filter, level } => self
83                .logs
84                .iter()
85                .filter(|entry| {
86                    entry.level <= *level
87                        && filter
88                            .as_ref()
89                            .map(|f| {
90                                if let Some(exclusion) = f.strip_prefix('-') {
91                                    !entry.module.contains(exclusion)
92                                } else {
93                                    entry.module.contains(f) || entry.message.contains(f)
94                                }
95                            })
96                            .unwrap_or(true)
97                })
98                .collect(),
99            _ => self.logs.iter().collect(),
100        }
101    }
102}