p2p_chat/ui/state/
chat.rs

1//! This module contains chat-related functionalities for the `UIState`.
2use chrono::Utc;
3
4use crate::types::Message;
5use crate::ui::mode::UIMode;
6
7use super::{ChatMessageEntry, UIState};
8
9impl UIState {
10    /// Adds a new `Message` to the UI state.
11    ///
12    /// This function stores the message along with its reception timestamp.
13    /// If the UI is in chat mode and at the bottom of the scroll, it resets
14    /// the scroll offset. Otherwise, it adjusts the scroll offset to keep
15    /// new messages visible if not explicitly scrolled up.
16    ///
17    /// # Arguments
18    ///
19    /// * `message` - The `Message` to add.
20    pub fn add_message(&mut self, message: Message) {
21        if self.contains_message(&message.id) {
22            return;
23        }
24        self.messages.push(ChatMessageEntry {
25            message,
26            received_at: Utc::now(),
27        });
28
29        if matches!(self.mode, UIMode::Chat) {
30            if self.is_at_bottom_chat {
31                self.scroll_offset = 0;
32            } else {
33                self.scroll_offset = self.scroll_offset.saturating_add(1);
34                self.update_chat_scroll_state(self.terminal_size.1 as usize);
35            }
36        }
37    }
38
39    /// Adds a generic chat message string to the UI state.
40    ///
41    /// This is typically used for system messages or user input echoes.
42    /// It handles scroll adjustment similarly to `add_message`.
43    ///
44    /// # Arguments
45    ///
46    /// * `message` - The string content of the chat message.
47    pub fn add_chat_message(&mut self, message: String) {
48        let line_count = message.lines().count();
49        self.chat_messages.push((Utc::now(), message));
50
51        if matches!(self.mode, UIMode::Chat) {
52            if self.is_at_bottom_chat {
53                self.scroll_offset = 0;
54            } else {
55                self.scroll_offset = self.scroll_offset.saturating_add(line_count);
56                self.update_chat_scroll_state(self.terminal_size.1 as usize);
57            }
58        }
59    }
60
61    /// Adds a block of history output to the UI state.
62    ///
63    /// This is used for displaying multi-line output from commands like `history`.
64    /// Each line is prefixed with a special marker for rendering purposes.
65    ///
66    /// # Arguments
67    ///
68    /// * `message` - The string content of the history output.
69    pub fn add_history_output(&mut self, message: String) {
70        let current_timestamp = Utc::now();
71        let line_count = message.lines().count();
72
73        for line in message.lines() {
74            let marked_line = if line.trim().is_empty() {
75                line.to_string()
76            } else {
77                format!("__HISTORY_OUTPUT__{}", line)
78            };
79            self.chat_messages.push((current_timestamp, marked_line));
80        }
81
82        if matches!(self.mode, UIMode::Chat) {
83            if self.is_at_bottom_chat {
84                self.scroll_offset = 0;
85            } else {
86                self.scroll_offset = self.scroll_offset.saturating_add(line_count);
87                self.update_chat_scroll_state(self.terminal_size.1 as usize);
88            }
89        }
90    }
91
92    /// Updates the chat scroll state based on the current terminal height.
93    ///
94    /// This ensures that the `scroll_offset` remains within valid bounds and
95    /// updates `is_at_bottom_chat`.
96    ///
97    /// # Arguments
98    ///
99    /// * `terminal_height` - The current height of the terminal in lines.
100    pub fn update_chat_scroll_state(&mut self, terminal_height: usize) {
101        let total_items = self.calculate_total_chat_items();
102        let visible_lines = terminal_height.saturating_sub(2); // Account for input and status lines
103        let max_scroll = if total_items > visible_lines {
104            total_items.saturating_sub(visible_lines)
105        } else {
106            0
107        };
108
109        self.scroll_offset = self.scroll_offset.min(max_scroll);
110        self.is_at_bottom_chat = self.scroll_offset == 0;
111    }
112
113    /// Calculates the total number of displayable items in the chat view.
114    ///
115    /// This includes both actual messages and generic chat messages.
116    ///
117    /// # Returns
118    ///
119    /// The total count of items that can be displayed.
120    pub fn calculate_total_chat_items(&self) -> usize {
121        let message_count = self.messages.len();
122        let chat_line_count: usize = self
123            .chat_messages
124            .iter()
125            .map(|(_, msg)| msg.lines().count())
126            .sum();
127
128        message_count + chat_line_count
129    }
130
131    /// Scrolls the chat view to the bottom.
132    pub fn jump_to_bottom_chat(&mut self) {
133        self.scroll_offset = 0;
134        self.is_at_bottom_chat = true;
135    }
136}