1use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum SessionStatus {
11 Active,
13 Idle,
15 Dirty,
17 Loading,
19 Error,
21}
22
23impl SessionStatus {
24 pub fn symbol(&self) -> &'static str {
26 match self {
27 SessionStatus::Active => "●",
28 SessionStatus::Idle => "○",
29 SessionStatus::Dirty => "◆",
30 SessionStatus::Loading => "◐",
31 SessionStatus::Error => "✕",
32 }
33 }
34
35 pub fn display_name(&self) -> &'static str {
37 match self {
38 SessionStatus::Active => "Active",
39 SessionStatus::Idle => "Idle",
40 SessionStatus::Dirty => "Dirty",
41 SessionStatus::Loading => "Loading",
42 SessionStatus::Error => "Error",
43 }
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct Session {
50 pub id: String,
52 pub name: String,
54 pub status: SessionStatus,
56 pub last_activity: u64,
58 pub message_count: usize,
60 pub has_changes: bool,
62 pub metadata: HashMap<String, String>,
64}
65
66impl Session {
67 pub fn new(id: String, name: String) -> Self {
69 Self {
70 id,
71 name,
72 status: SessionStatus::Idle,
73 last_activity: 0,
74 message_count: 0,
75 has_changes: false,
76 metadata: HashMap::new(),
77 }
78 }
79
80 pub fn set_status(&mut self, status: SessionStatus) {
82 self.status = status;
83 }
84
85 pub fn mark_dirty(&mut self) {
87 self.has_changes = true;
88 self.status = SessionStatus::Dirty;
89 }
90
91 pub fn mark_clean(&mut self) {
93 self.has_changes = false;
94 if self.status == SessionStatus::Dirty {
95 self.status = SessionStatus::Idle;
96 }
97 }
98
99 pub fn update_activity(&mut self) {
101 self.last_activity = std::time::SystemTime::now()
102 .duration_since(std::time::UNIX_EPOCH)
103 .map(|d| d.as_secs())
104 .unwrap_or(0);
105 }
106
107 pub fn add_message(&mut self) {
109 self.message_count += 1;
110 self.update_activity();
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum SessionDisplayMode {
117 Tabs,
119 List,
121}
122
123#[derive(Debug, Clone)]
125pub struct SessionWidget {
126 pub sessions: Vec<Session>,
128 pub selected_index: usize,
130 pub display_mode: SessionDisplayMode,
132 pub visible: bool,
134 pub scroll_offset: usize,
136 pub max_visible: usize,
138}
139
140impl SessionWidget {
141 pub fn new() -> Self {
143 Self {
144 sessions: Vec::new(),
145 selected_index: 0,
146 display_mode: SessionDisplayMode::Tabs,
147 visible: true,
148 scroll_offset: 0,
149 max_visible: 10,
150 }
151 }
152
153 pub fn add_session(&mut self, session: Session) {
155 self.sessions.push(session);
156 self.selected_index = self.sessions.len() - 1;
158 }
159
160 pub fn remove_session(&mut self, index: usize) -> Option<Session> {
162 if index < self.sessions.len() {
163 let removed = self.sessions.remove(index);
164
165 if self.selected_index >= self.sessions.len() && !self.sessions.is_empty() {
167 self.selected_index = self.sessions.len() - 1;
168 }
169
170 Some(removed)
171 } else {
172 None
173 }
174 }
175
176 pub fn current_session(&self) -> Option<&Session> {
178 self.sessions.get(self.selected_index)
179 }
180
181 pub fn current_session_mut(&mut self) -> Option<&mut Session> {
183 self.sessions.get_mut(self.selected_index)
184 }
185
186 pub fn switch_to_session(&mut self, index: usize) -> bool {
188 if index < self.sessions.len() {
189 self.selected_index = index;
190 if let Some(session) = self.current_session_mut() {
191 session.set_status(SessionStatus::Active);
192 }
193 true
194 } else {
195 false
196 }
197 }
198
199 pub fn switch_to_session_by_id(&mut self, id: &str) -> bool {
201 if let Some(index) = self.sessions.iter().position(|s| s.id == id) {
202 self.switch_to_session(index)
203 } else {
204 false
205 }
206 }
207
208 pub fn next_session(&mut self) -> bool {
210 if self.sessions.is_empty() {
211 return false;
212 }
213 let next_index = (self.selected_index + 1) % self.sessions.len();
214 self.switch_to_session(next_index);
215 true
216 }
217
218 pub fn previous_session(&mut self) -> bool {
220 if self.sessions.is_empty() {
221 return false;
222 }
223 let prev_index = if self.selected_index == 0 {
224 self.sessions.len() - 1
225 } else {
226 self.selected_index - 1
227 };
228 self.switch_to_session(prev_index);
229 true
230 }
231
232 pub fn rename_session(&mut self, index: usize, new_name: String) -> bool {
234 if let Some(session) = self.sessions.get_mut(index) {
235 session.name = new_name;
236 true
237 } else {
238 false
239 }
240 }
241
242 pub fn rename_current_session(&mut self, new_name: String) -> bool {
244 self.rename_session(self.selected_index, new_name)
245 }
246
247 pub fn toggle_display_mode(&mut self) {
249 self.display_mode = match self.display_mode {
250 SessionDisplayMode::Tabs => SessionDisplayMode::List,
251 SessionDisplayMode::List => SessionDisplayMode::Tabs,
252 };
253 }
254
255 pub fn set_display_mode(&mut self, mode: SessionDisplayMode) {
257 self.display_mode = mode;
258 }
259
260 pub fn toggle_visibility(&mut self) {
262 self.visible = !self.visible;
263 }
264
265 pub fn show(&mut self) {
267 self.visible = true;
268 }
269
270 pub fn hide(&mut self) {
272 self.visible = false;
273 }
274
275 pub fn scroll_up(&mut self) {
277 if self.scroll_offset > 0 {
278 self.scroll_offset -= 1;
279 }
280 }
281
282 pub fn scroll_down(&mut self) {
284 let max_scroll = self.sessions.len().saturating_sub(self.max_visible);
285 if self.scroll_offset < max_scroll {
286 self.scroll_offset += 1;
287 }
288 }
289
290 pub fn visible_sessions(&self) -> Vec<&Session> {
292 self.sessions
293 .iter()
294 .skip(self.scroll_offset)
295 .take(self.max_visible)
296 .collect()
297 }
298
299 pub fn session_count(&self) -> usize {
301 self.sessions.len()
302 }
303
304 pub fn has_sessions(&self) -> bool {
306 !self.sessions.is_empty()
307 }
308
309 pub fn clear(&mut self) {
311 self.sessions.clear();
312 self.selected_index = 0;
313 self.scroll_offset = 0;
314 }
315
316 pub fn get_session(&self, id: &str) -> Option<&Session> {
318 self.sessions.iter().find(|s| s.id == id)
319 }
320
321 pub fn get_session_mut(&mut self, id: &str) -> Option<&mut Session> {
323 self.sessions.iter_mut().find(|s| s.id == id)
324 }
325
326 pub fn session_ids(&self) -> Vec<&str> {
328 self.sessions.iter().map(|s| s.id.as_str()).collect()
329 }
330
331 pub fn session_names(&self) -> Vec<&str> {
333 self.sessions.iter().map(|s| s.name.as_str()).collect()
334 }
335
336 pub fn find_session_index(&self, id: &str) -> Option<usize> {
338 self.sessions.iter().position(|s| s.id == id)
339 }
340
341 pub fn selected_index(&self) -> usize {
343 self.selected_index
344 }
345
346 pub fn display_mode(&self) -> SessionDisplayMode {
348 self.display_mode
349 }
350
351 pub fn is_visible(&self) -> bool {
353 self.visible
354 }
355}
356
357impl Default for SessionWidget {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
368 fn test_session_creation() {
369 let session = Session::new("session-1".to_string(), "Session 1".to_string());
370 assert_eq!(session.id, "session-1");
371 assert_eq!(session.name, "Session 1");
372 assert_eq!(session.status, SessionStatus::Idle);
373 assert_eq!(session.message_count, 0);
374 assert!(!session.has_changes);
375 }
376
377 #[test]
378 fn test_session_status_changes() {
379 let mut session = Session::new("session-1".to_string(), "Session 1".to_string());
380
381 session.set_status(SessionStatus::Active);
382 assert_eq!(session.status, SessionStatus::Active);
383
384 session.mark_dirty();
385 assert_eq!(session.status, SessionStatus::Dirty);
386 assert!(session.has_changes);
387
388 session.mark_clean();
389 assert!(!session.has_changes);
390 }
391
392 #[test]
393 fn test_session_widget_add_session() {
394 let mut widget = SessionWidget::new();
395 assert_eq!(widget.session_count(), 0);
396
397 let session = Session::new("session-1".to_string(), "Session 1".to_string());
398 widget.add_session(session);
399
400 assert_eq!(widget.session_count(), 1);
401 assert_eq!(widget.selected_index(), 0);
402 }
403
404 #[test]
405 fn test_session_widget_switch_session() {
406 let mut widget = SessionWidget::new();
407
408 widget.add_session(Session::new(
409 "session-1".to_string(),
410 "Session 1".to_string(),
411 ));
412 widget.add_session(Session::new(
413 "session-2".to_string(),
414 "Session 2".to_string(),
415 ));
416
417 assert_eq!(widget.selected_index(), 1);
419
420 widget.switch_to_session(0);
421 assert_eq!(widget.selected_index(), 0);
422
423 let current = widget.current_session().unwrap();
424 assert_eq!(current.id, "session-1");
425 }
426
427 #[test]
428 fn test_session_widget_next_previous() {
429 let mut widget = SessionWidget::new();
430
431 widget.add_session(Session::new(
432 "session-1".to_string(),
433 "Session 1".to_string(),
434 ));
435 widget.add_session(Session::new(
436 "session-2".to_string(),
437 "Session 2".to_string(),
438 ));
439 widget.add_session(Session::new(
440 "session-3".to_string(),
441 "Session 3".to_string(),
442 ));
443
444 assert_eq!(widget.selected_index(), 2);
446
447 widget.next_session();
448 assert_eq!(widget.selected_index(), 0); widget.next_session();
451 assert_eq!(widget.selected_index(), 1);
452
453 widget.next_session();
454 assert_eq!(widget.selected_index(), 2);
455
456 widget.previous_session();
457 assert_eq!(widget.selected_index(), 1);
458 }
459
460 #[test]
461 fn test_session_widget_remove_session() {
462 let mut widget = SessionWidget::new();
463
464 widget.add_session(Session::new(
465 "session-1".to_string(),
466 "Session 1".to_string(),
467 ));
468 widget.add_session(Session::new(
469 "session-2".to_string(),
470 "Session 2".to_string(),
471 ));
472
473 assert_eq!(widget.session_count(), 2);
474
475 let removed = widget.remove_session(0);
476 assert!(removed.is_some());
477 assert_eq!(widget.session_count(), 1);
478 assert_eq!(widget.selected_index(), 0);
479 }
480
481 #[test]
482 fn test_session_widget_rename() {
483 let mut widget = SessionWidget::new();
484
485 widget.add_session(Session::new(
486 "session-1".to_string(),
487 "Session 1".to_string(),
488 ));
489
490 widget.rename_current_session("New Name".to_string());
491
492 let current = widget.current_session().unwrap();
493 assert_eq!(current.name, "New Name");
494 }
495
496 #[test]
497 fn test_session_widget_display_mode() {
498 let mut widget = SessionWidget::new();
499
500 assert_eq!(widget.display_mode(), SessionDisplayMode::Tabs);
501
502 widget.toggle_display_mode();
503 assert_eq!(widget.display_mode(), SessionDisplayMode::List);
504
505 widget.set_display_mode(SessionDisplayMode::Tabs);
506 assert_eq!(widget.display_mode(), SessionDisplayMode::Tabs);
507 }
508
509 #[test]
510 fn test_session_widget_visibility() {
511 let mut widget = SessionWidget::new();
512
513 assert!(widget.is_visible());
514
515 widget.hide();
516 assert!(!widget.is_visible());
517
518 widget.show();
519 assert!(widget.is_visible());
520
521 widget.toggle_visibility();
522 assert!(!widget.is_visible());
523 }
524
525 #[test]
526 fn test_session_status_symbols() {
527 assert_eq!(SessionStatus::Active.symbol(), "●");
528 assert_eq!(SessionStatus::Idle.symbol(), "○");
529 assert_eq!(SessionStatus::Dirty.symbol(), "◆");
530 assert_eq!(SessionStatus::Loading.symbol(), "◐");
531 assert_eq!(SessionStatus::Error.symbol(), "✕");
532 }
533
534 #[test]
535 fn test_session_widget_scroll() {
536 let mut widget = SessionWidget::new();
537 widget.max_visible = 3;
538
539 for i in 0..10 {
540 widget.add_session(Session::new(
541 format!("session-{}", i),
542 format!("Session {}", i),
543 ));
544 }
545
546 assert_eq!(widget.scroll_offset, 0);
547
548 widget.scroll_down();
549 assert_eq!(widget.scroll_offset, 1);
550
551 widget.scroll_up();
552 assert_eq!(widget.scroll_offset, 0);
553 }
554}