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}