boxmux_lib/components/
status_indicator.rs1use crate::model::muxbox::MuxBox;
2use crate::pty_manager::PtyManager;
3
4pub struct StatusIndicator {
6 pub indicator_type: StatusType,
7 pub process_info: Option<String>,
8 pub custom_text: Option<String>,
9}
10
11#[derive(Debug, Clone, PartialEq)]
13pub enum StatusType {
14 PtyNormal,
16 PtyDead,
18 PtyError,
20 ScriptRunning,
22 ScriptCompleted,
24 ScriptFailed,
26 Custom(String),
28 None,
30}
31
32impl StatusType {
33 pub fn get_indicator(&self) -> &str {
35 match self {
36 StatusType::PtyNormal => "⚡",
37 StatusType::PtyDead => "💀",
38 StatusType::PtyError => "⚠️",
39 StatusType::ScriptRunning => "▶️",
40 StatusType::ScriptCompleted => "✅",
41 StatusType::ScriptFailed => "❌",
42 StatusType::Custom(indicator) => indicator,
43 StatusType::None => "",
44 }
45 }
46
47 pub fn is_visible(&self) -> bool {
49 !matches!(self, StatusType::None)
50 }
51}
52
53impl StatusIndicator {
54 pub fn from_muxbox(muxbox: &MuxBox, pty_manager: Option<&PtyManager>) -> Self {
56 if muxbox.execution_mode.is_pty() {
57 let indicator_type = if let Some(pty_manager) = pty_manager {
58 if pty_manager.is_pty_dead(&muxbox.id) {
59 StatusType::PtyDead
60 } else if pty_manager.is_pty_in_error_state(&muxbox.id) {
61 StatusType::PtyError
62 } else {
63 StatusType::PtyNormal
64 }
65 } else {
66 StatusType::PtyNormal
67 };
68
69 let process_info = pty_manager.and_then(|pm| pm.get_process_status_summary(&muxbox.id));
70
71 Self {
72 indicator_type,
73 process_info,
74 custom_text: None,
75 }
76 } else {
77 Self {
78 indicator_type: StatusType::None,
79 process_info: None,
80 custom_text: None,
81 }
82 }
83 }
84
85 pub fn new_custom(indicator: String, text: Option<String>) -> Self {
87 Self {
88 indicator_type: StatusType::Custom(indicator),
89 process_info: None,
90 custom_text: text,
91 }
92 }
93
94 pub fn new(status_type: StatusType) -> Self {
96 Self {
97 indicator_type: status_type,
98 process_info: None,
99 custom_text: None,
100 }
101 }
102
103 pub fn with_process_info(mut self, info: String) -> Self {
105 self.process_info = Some(info);
106 self
107 }
108
109 pub fn with_custom_text(mut self, text: String) -> Self {
111 self.custom_text = Some(text);
112 self
113 }
114
115 pub fn render_for_title(&self, original_title: Option<&str>) -> Option<String> {
117 if !self.indicator_type.is_visible() {
118 return original_title.map(|s| s.to_string());
119 }
120
121 let mut title_parts = vec![self.indicator_type.get_indicator().to_string()];
122
123 if let Some(ref info) = self.process_info {
125 title_parts.push(format!("[{}]", info));
126 }
127
128 if let Some(ref text) = self.custom_text {
130 title_parts.push(text.clone());
131 }
132
133 if let Some(title) = original_title {
135 title_parts.push(title.to_string());
136 } else if matches!(
137 self.indicator_type,
138 StatusType::PtyNormal | StatusType::PtyDead | StatusType::PtyError
139 ) {
140 title_parts.push("PTY".to_string());
141 }
142
143 Some(title_parts.join(" "))
144 }
145
146 pub fn get_indicator(&self) -> &str {
148 self.indicator_type.get_indicator()
149 }
150
151 pub fn is_visible(&self) -> bool {
153 self.indicator_type.is_visible()
154 }
155
156 pub fn get_full_status(&self) -> String {
158 if !self.is_visible() {
159 return String::new();
160 }
161
162 let mut parts = vec![self.get_indicator().to_string()];
163
164 if let Some(ref info) = self.process_info {
165 parts.push(format!("[{}]", info));
166 }
167
168 if let Some(ref text) = self.custom_text {
169 parts.push(text.clone());
170 }
171
172 parts.join(" ")
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use crate::model::common::ExecutionMode;
180
181 #[test]
182 fn test_status_type_indicators() {
183 assert_eq!(StatusType::PtyNormal.get_indicator(), "⚡");
184 assert_eq!(StatusType::PtyDead.get_indicator(), "💀");
185 assert_eq!(StatusType::PtyError.get_indicator(), "⚠️");
186 assert_eq!(StatusType::ScriptRunning.get_indicator(), "▶️");
187 assert_eq!(StatusType::ScriptCompleted.get_indicator(), "✅");
188 assert_eq!(StatusType::ScriptFailed.get_indicator(), "❌");
189 assert_eq!(StatusType::Custom("🚀".to_string()).get_indicator(), "🚀");
190 assert_eq!(StatusType::None.get_indicator(), "");
191 }
192
193 #[test]
194 fn test_status_type_visibility() {
195 assert!(StatusType::PtyNormal.is_visible());
196 assert!(StatusType::PtyDead.is_visible());
197 assert!(StatusType::PtyError.is_visible());
198 assert!(StatusType::Custom("⭐".to_string()).is_visible());
199 assert!(!StatusType::None.is_visible());
200 }
201
202 #[test]
203 fn test_custom_status_indicator() {
204 let indicator =
205 StatusIndicator::new_custom("🎯".to_string(), Some("Custom Status".to_string()));
206 assert_eq!(indicator.get_indicator(), "🎯");
207 assert_eq!(indicator.custom_text, Some("Custom Status".to_string()));
208 assert!(indicator.is_visible());
209 }
210
211 #[test]
212 fn test_status_indicator_with_process_info() {
213 let indicator =
214 StatusIndicator::new(StatusType::PtyNormal).with_process_info("bash:1234".to_string());
215
216 assert_eq!(indicator.get_indicator(), "⚡");
217 assert_eq!(indicator.process_info, Some("bash:1234".to_string()));
218 }
219
220 #[test]
221 fn test_render_for_title() {
222 let indicator =
223 StatusIndicator::new(StatusType::PtyNormal).with_process_info("bash:1234".to_string());
224
225 let result = indicator.render_for_title(Some("My Terminal"));
226 assert_eq!(result, Some("⚡ [bash:1234] My Terminal".to_string()));
227 }
228
229 #[test]
230 fn test_render_for_title_no_original() {
231 let indicator =
232 StatusIndicator::new(StatusType::PtyNormal).with_process_info("bash:1234".to_string());
233
234 let result = indicator.render_for_title(None);
235 assert_eq!(result, Some("⚡ [bash:1234] PTY".to_string()));
236 }
237
238 #[test]
239 fn test_render_for_title_none_status() {
240 let indicator = StatusIndicator::new(StatusType::None);
241 let result = indicator.render_for_title(Some("Regular Title"));
242 assert_eq!(result, Some("Regular Title".to_string()));
243 }
244
245 #[test]
246 fn test_get_full_status() {
247 let indicator = StatusIndicator::new(StatusType::PtyError)
248 .with_process_info("failed_cmd".to_string())
249 .with_custom_text("Connection Lost".to_string());
250
251 let result = indicator.get_full_status();
252 assert_eq!(result, "⚠️ [failed_cmd] Connection Lost");
253 }
254
255 #[test]
256 fn test_from_muxbox_pty() {
257 let mut muxbox = MuxBox::default();
258 muxbox.id = "test_pty".to_string();
259 muxbox.execution_mode = ExecutionMode::Pty;
260
261 let indicator = StatusIndicator::from_muxbox(&muxbox, None);
262 assert!(matches!(indicator.indicator_type, StatusType::PtyNormal));
263 assert_eq!(indicator.get_indicator(), "⚡");
264 }
265
266 #[test]
267 fn test_from_muxbox_non_pty() {
268 let mut muxbox = MuxBox::default();
269 muxbox.execution_mode = ExecutionMode::Thread;
270
271 let indicator = StatusIndicator::from_muxbox(&muxbox, None);
272 assert!(matches!(indicator.indicator_type, StatusType::None));
273 assert!(!indicator.is_visible());
274 }
275}