1use crate::clipboard::{CopyFeedback, CopyOperation};
36
37#[derive(Debug, Clone)]
39pub struct Message {
40 pub content: String,
42 pub author: MessageAuthor,
44 pub streaming: bool,
46}
47
48#[derive(Debug, Clone)]
50pub struct StreamingMessage {
51 pub content: String,
53 pub active: bool,
55 pub cursor_pos: usize,
57 pub animation_frame: u32,
59 pub token_count: usize,
61}
62
63impl Default for StreamingMessage {
64 fn default() -> Self {
65 Self {
66 content: String::new(),
67 active: true,
68 cursor_pos: 0,
69 animation_frame: 0,
70 token_count: 0,
71 }
72 }
73}
74
75impl StreamingMessage {
76 pub fn new() -> Self {
78 Self::default()
79 }
80
81 pub fn append(&mut self, token: &str) {
83 self.content.push_str(token);
84 self.cursor_pos = self.content.len();
85 self.token_count += 1;
86 }
87
88 pub fn finish(&mut self) {
90 self.active = false;
91 }
92
93 pub fn update_animation(&mut self) {
95 if self.active {
96 self.animation_frame = (self.animation_frame + 1) % 4;
97 }
98 }
99
100 pub fn display_text(&self) -> String {
102 if self.active {
103 let cursor = if self.animation_frame < 2 { "_" } else { " " };
105 format!("{}{}", self.content, cursor)
106 } else {
107 self.content.clone()
108 }
109 }
110
111 pub fn display_text_with_cursor(&self, cursor_char: &str) -> String {
113 if self.active {
114 format!("{}{}", self.content, cursor_char)
115 } else {
116 self.content.clone()
117 }
118 }
119
120 pub fn is_complete(&self) -> bool {
122 !self.active
123 }
124
125 pub fn len(&self) -> usize {
127 self.content.len()
128 }
129
130 pub fn is_empty(&self) -> bool {
132 self.content.is_empty()
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub enum MessageAuthor {
139 User,
141 Assistant,
143}
144
145impl Message {
146 pub fn user(content: impl Into<String>) -> Self {
148 Self {
149 content: content.into(),
150 author: MessageAuthor::User,
151 streaming: false,
152 }
153 }
154
155 pub fn assistant(content: impl Into<String>) -> Self {
157 Self {
158 content: content.into(),
159 author: MessageAuthor::Assistant,
160 streaming: false,
161 }
162 }
163
164 pub fn extract_code_blocks(&self) -> Vec<String> {
166 let mut code_blocks = Vec::new();
167 let mut in_code_block = false;
168 let mut current_block = String::new();
169
170 for line in self.content.lines() {
171 if line.starts_with("```") {
172 if in_code_block {
173 if !current_block.is_empty() {
175 code_blocks.push(current_block.clone());
176 current_block.clear();
177 }
178 in_code_block = false;
179 } else {
180 in_code_block = true;
182 }
183 } else if in_code_block {
184 if !current_block.is_empty() {
185 current_block.push('\n');
186 }
187 current_block.push_str(line);
188 }
189 }
190
191 code_blocks
192 }
193
194 pub fn get_first_code_block(&self) -> Option<String> {
196 self.extract_code_blocks().into_iter().next()
197 }
198
199 pub fn has_code_blocks(&self) -> bool {
201 self.content.contains("```")
202 }
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum MessageAction {
208 Copy,
210 CopyCode,
212 Edit,
214 Regenerate,
216 Delete,
218}
219
220pub struct ChatWidget {
222 pub messages: Vec<Message>,
224 pub input: String,
226 pub scroll: usize,
228 pub selected: Option<usize>,
230 pub available_actions: Vec<MessageAction>,
232 pub streaming_message: Option<StreamingMessage>,
234 pub is_streaming: bool,
236 pub copy_operation: Option<CopyOperation>,
238 pub show_action_menu: bool,
240 pub selected_action: Option<usize>,
242}
243
244impl ChatWidget {
245 pub fn new() -> Self {
247 Self {
248 messages: Vec::new(),
249 input: String::new(),
250 scroll: 0,
251 selected: None,
252 available_actions: Vec::new(),
253 streaming_message: None,
254 is_streaming: false,
255 copy_operation: None,
256 show_action_menu: false,
257 selected_action: None,
258 }
259 }
260
261 pub fn start_streaming(&mut self) {
263 self.streaming_message = Some(StreamingMessage::new());
264 self.is_streaming = true;
265 }
266
267 pub fn append_token(&mut self, token: &str) {
269 if let Some(ref mut msg) = self.streaming_message {
270 msg.append(token);
271 }
272 }
273
274 pub fn finish_streaming(&mut self) -> Option<Message> {
276 if let Some(mut msg) = self.streaming_message.take() {
277 msg.finish();
278 let content = msg.content.clone();
279 self.is_streaming = false;
280
281 let message = Message::assistant(content);
283 self.messages.push(message.clone());
284 return Some(message);
285 }
286 None
287 }
288
289 pub fn update_streaming_animation(&mut self) {
291 if let Some(ref mut msg) = self.streaming_message {
292 msg.update_animation();
293 }
294 }
295
296 pub fn get_streaming_display(&self) -> Option<String> {
298 self.streaming_message
299 .as_ref()
300 .map(|msg| msg.display_text())
301 }
302
303 pub fn cancel_streaming(&mut self) {
305 self.streaming_message = None;
306 self.is_streaming = false;
307 }
308
309 pub fn add_message(&mut self, message: Message) {
311 self.messages.push(message);
312 }
313
314 pub fn clear(&mut self) {
316 self.messages.clear();
317 self.input.clear();
318 self.scroll = 0;
319 self.selected = None;
320 self.available_actions.clear();
321 self.streaming_message = None;
322 self.is_streaming = false;
323 self.copy_operation = None;
324 self.show_action_menu = false;
325 self.selected_action = None;
326 }
327
328 pub fn update_actions(&mut self) {
330 self.available_actions.clear();
331
332 if let Some(idx) = self.selected {
333 if let Some(msg) = self.messages.get(idx) {
334 self.available_actions.push(MessageAction::Copy);
335
336 if msg.content.contains("```") {
337 self.available_actions.push(MessageAction::CopyCode);
338 }
339
340 if msg.author == MessageAuthor::User {
341 self.available_actions.push(MessageAction::Edit);
342 } else {
343 self.available_actions.push(MessageAction::Regenerate);
344 }
345
346 self.available_actions.push(MessageAction::Delete);
347 }
348 }
349 }
350
351 pub fn execute_action(&mut self, action: MessageAction) -> Result<(), String> {
353 match action {
354 MessageAction::Copy => {
355 if let Some(msg) = self.selected_message() {
356 let content = msg.content.clone();
357 let mut op = CopyOperation::new(content);
358 match op.execute() {
359 Ok(()) => {
360 tracing::info!("Copied message to clipboard");
361 self.copy_operation = Some(op);
362 Ok(())
363 }
364 Err(e) => {
365 tracing::error!("Failed to copy message: {}", e);
366 Err(format!("Failed to copy: {}", e))
367 }
368 }
369 } else {
370 Err("No message selected".to_string())
371 }
372 }
373 MessageAction::CopyCode => {
374 if let Some(msg) = self.selected_message() {
375 if let Some(code) = msg.get_first_code_block() {
376 let mut op = CopyOperation::new(code);
377 match op.execute() {
378 Ok(()) => {
379 tracing::info!("Copied code block to clipboard");
380 self.copy_operation = Some(op);
381 Ok(())
382 }
383 Err(e) => {
384 tracing::error!("Failed to copy code: {}", e);
385 Err(format!("Failed to copy code: {}", e))
386 }
387 }
388 } else {
389 Err("No code block found in message".to_string())
390 }
391 } else {
392 Err("No message selected".to_string())
393 }
394 }
395 MessageAction::Edit => {
396 if let Some(idx) = self.selected {
397 if let Some(msg) = self.messages.get_mut(idx) {
398 if msg.author == MessageAuthor::User {
399 self.input = msg.content.clone();
400 tracing::info!("Editing message");
401 self.show_action_menu = false;
402 return Ok(());
403 }
404 }
405 }
406 Err("Cannot edit non-user messages".to_string())
407 }
408 MessageAction::Regenerate => {
409 if let Some(msg) = self.selected_message() {
410 if msg.author == MessageAuthor::Assistant {
411 tracing::info!("Regenerating response");
412 self.show_action_menu = false;
413 return Ok(());
414 }
415 }
416 Err("Can only regenerate assistant messages".to_string())
417 }
418 MessageAction::Delete => {
419 if let Some(idx) = self.selected {
420 self.messages.remove(idx);
421 self.selected = None;
422 self.available_actions.clear();
423 self.show_action_menu = false;
424 tracing::info!("Deleted message");
425 return Ok(());
426 }
427 Err("No message selected".to_string())
428 }
429 }
430 }
431
432 pub fn visible_messages(&self, height: usize) -> Vec<&Message> {
434 self.messages
435 .iter()
436 .skip(self.scroll)
437 .take(height)
438 .collect()
439 }
440
441 pub fn scroll_up(&mut self) {
443 if self.scroll > 0 {
444 self.scroll -= 1;
445 }
446 }
447
448 pub fn scroll_down(&mut self, height: usize) {
450 let max_scroll = self.messages.len().saturating_sub(height);
451 if self.scroll < max_scroll {
452 self.scroll += 1;
453 }
454 }
455
456 pub fn select_next(&mut self) {
458 match self.selected {
459 None => self.selected = Some(0),
460 Some(idx) if idx < self.messages.len() - 1 => self.selected = Some(idx + 1),
461 _ => {}
462 }
463 }
464
465 pub fn select_prev(&mut self) {
467 match self.selected {
468 None => {}
469 Some(0) => self.selected = None,
470 Some(idx) => self.selected = Some(idx - 1),
471 }
472 }
473
474 pub fn selected_message(&self) -> Option<&Message> {
476 self.selected.and_then(|idx| self.messages.get(idx))
477 }
478
479 pub fn toggle_action_menu(&mut self) {
481 if self.selected.is_some() && !self.available_actions.is_empty() {
482 self.show_action_menu = !self.show_action_menu;
483 if self.show_action_menu {
484 self.selected_action = Some(0);
485 } else {
486 self.selected_action = None;
487 }
488 }
489 }
490
491 pub fn close_action_menu(&mut self) {
493 self.show_action_menu = false;
494 self.selected_action = None;
495 }
496
497 pub fn action_menu_up(&mut self) {
499 if let Some(idx) = self.selected_action {
500 if idx > 0 {
501 self.selected_action = Some(idx - 1);
502 }
503 }
504 }
505
506 pub fn action_menu_down(&mut self) {
508 if let Some(idx) = self.selected_action {
509 if idx < self.available_actions.len() - 1 {
510 self.selected_action = Some(idx + 1);
511 }
512 }
513 }
514
515 pub fn execute_action_by_shortcut(&mut self, key: char) -> Result<(), String> {
517 let action = match key {
518 'c' | 'C' => MessageAction::Copy,
519 'o' | 'O' => MessageAction::CopyCode,
520 'e' | 'E' => MessageAction::Edit,
521 'r' | 'R' => MessageAction::Regenerate,
522 'd' | 'D' => MessageAction::Delete,
523 _ => return Err(format!("Unknown shortcut: {}", key)),
524 };
525
526 if self.available_actions.contains(&action) {
527 self.execute_action(action)
528 } else {
529 Err(format!("Action not available: {:?}", action))
530 }
531 }
532
533 pub fn get_selected_action(&self) -> Option<MessageAction> {
535 self.selected_action
536 .and_then(|idx| self.available_actions.get(idx))
537 .copied()
538 }
539
540 pub fn execute_selected_action(&mut self) -> Result<(), String> {
542 if let Some(action) = self.get_selected_action() {
543 self.execute_action(action)
544 } else {
545 Err("No action selected".to_string())
546 }
547 }
548
549 pub fn update_copy_feedback(&mut self) {
551 if let Some(ref mut op) = self.copy_operation {
552 op.update_feedback();
553 if !op.is_feedback_visible() {
554 self.copy_operation = None;
555 }
556 }
557 }
558
559 pub fn get_copy_feedback(&self) -> Option<CopyFeedback> {
561 self.copy_operation
562 .as_ref()
563 .and_then(|op| op.get_feedback())
564 }
565
566 pub fn is_copy_feedback_visible(&self) -> bool {
568 self.copy_operation
569 .as_ref()
570 .map(|op| op.is_feedback_visible())
571 .unwrap_or(false)
572 }
573}
574
575impl Default for ChatWidget {
576 fn default() -> Self {
577 Self::new()
578 }
579}
580
581pub struct PromptWidget;
585
586impl PromptWidget {
587 pub fn new() -> Self {
589 Self
590 }
591}
592
593impl Default for PromptWidget {
594 fn default() -> Self {
595 Self::new()
596 }
597}
598
599pub struct MenuWidget;
601
602impl MenuWidget {
603 pub fn new() -> Self {
605 Self
606 }
607}
608
609impl Default for MenuWidget {
610 fn default() -> Self {
611 Self::new()
612 }
613}
614
615pub struct ListWidget;
617
618impl ListWidget {
619 pub fn new() -> Self {
621 Self
622 }
623}
624
625impl Default for ListWidget {
626 fn default() -> Self {
627 Self::new()
628 }
629}
630
631pub struct DialogWidget;
633
634impl DialogWidget {
635 pub fn new() -> Self {
637 Self
638 }
639}
640
641impl Default for DialogWidget {
642 fn default() -> Self {
643 Self::new()
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650
651 #[test]
652 fn test_message_creation() {
653 let user_msg = Message::user("Hello");
654 assert_eq!(user_msg.content, "Hello");
655 assert_eq!(user_msg.author, MessageAuthor::User);
656 assert!(!user_msg.streaming);
657
658 let ai_msg = Message::assistant("Hi there");
659 assert_eq!(ai_msg.content, "Hi there");
660 assert_eq!(ai_msg.author, MessageAuthor::Assistant);
661 }
662
663 #[test]
664 fn test_chat_widget_creation() {
665 let widget = ChatWidget::new();
666 assert!(widget.messages.is_empty());
667 assert!(widget.input.is_empty());
668 assert_eq!(widget.scroll, 0);
669 assert!(widget.selected.is_none());
670 }
671
672 #[test]
673 fn test_add_message() {
674 let mut widget = ChatWidget::new();
675 widget.add_message(Message::user("Hello"));
676 widget.add_message(Message::assistant("Hi"));
677
678 assert_eq!(widget.messages.len(), 2);
679 assert_eq!(widget.messages[0].author, MessageAuthor::User);
680 assert_eq!(widget.messages[1].author, MessageAuthor::Assistant);
681 }
682
683 #[test]
684 fn test_clear() {
685 let mut widget = ChatWidget::new();
686 widget.add_message(Message::user("Hello"));
687 widget.input = "test".to_string();
688 widget.scroll = 5;
689
690 widget.clear();
691 assert!(widget.messages.is_empty());
692 assert!(widget.input.is_empty());
693 assert_eq!(widget.scroll, 0);
694 }
695
696 #[test]
697 fn test_scroll() {
698 let mut widget = ChatWidget::new();
699 for i in 0..10 {
700 widget.add_message(Message::user(format!("Message {}", i)));
701 }
702
703 widget.scroll_down(5);
704 assert_eq!(widget.scroll, 1);
705
706 widget.scroll_up();
707 assert_eq!(widget.scroll, 0);
708
709 widget.scroll_up();
710 assert_eq!(widget.scroll, 0);
711 }
712
713 #[test]
714 fn test_visible_messages() {
715 let mut widget = ChatWidget::new();
716 for i in 0..10 {
717 widget.add_message(Message::user(format!("Message {}", i)));
718 }
719
720 let visible = widget.visible_messages(5);
721 assert_eq!(visible.len(), 5);
722
723 widget.scroll = 5;
724 let visible = widget.visible_messages(5);
725 assert_eq!(visible.len(), 5);
726 }
727
728 #[test]
729 fn test_selection() {
730 let mut widget = ChatWidget::new();
731 widget.add_message(Message::user("Message 1"));
732 widget.add_message(Message::user("Message 2"));
733 widget.add_message(Message::user("Message 3"));
734
735 assert!(widget.selected_message().is_none());
736
737 widget.select_next();
738 assert_eq!(widget.selected, Some(0));
739
740 widget.select_next();
741 assert_eq!(widget.selected, Some(1));
742
743 widget.select_prev();
744 assert_eq!(widget.selected, Some(0));
745
746 widget.select_prev();
747 assert!(widget.selected.is_none());
748 }
749
750 #[test]
751 fn test_streaming_message_creation() {
752 let msg = StreamingMessage::new();
753 assert!(msg.active);
754 assert!(msg.content.is_empty());
755 assert_eq!(msg.cursor_pos, 0);
756 }
757
758 #[test]
759 fn test_streaming_message_append() {
760 let mut msg = StreamingMessage::new();
761 msg.append("Hello");
762 assert_eq!(msg.content, "Hello");
763 assert_eq!(msg.cursor_pos, 5);
764
765 msg.append(" world");
766 assert_eq!(msg.content, "Hello world");
767 assert_eq!(msg.cursor_pos, 11);
768 }
769
770 #[test]
771 fn test_streaming_message_display() {
772 let mut msg = StreamingMessage::new();
773 msg.append("Hello");
774 assert_eq!(msg.display_text(), "Hello_");
775
776 msg.finish();
777 assert_eq!(msg.display_text(), "Hello");
778 assert!(!msg.active);
779 }
780
781 #[test]
782 fn test_message_actions() {
783 let mut widget = ChatWidget::new();
784 widget.add_message(Message::user("Hello"));
785 widget.add_message(Message::assistant("Hi there"));
786
787 widget.select_next();
788 widget.update_actions();
789
790 assert!(!widget.available_actions.is_empty());
791 assert!(widget.available_actions.contains(&MessageAction::Copy));
792 assert!(widget.available_actions.contains(&MessageAction::Edit));
793 }
794
795 #[test]
796 fn test_execute_copy_action() {
797 let mut widget = ChatWidget::new();
798 widget.add_message(Message::user("Hello"));
799 widget.select_next();
800
801 let result = widget.execute_action(MessageAction::Copy);
802 let _ = result;
804 }
805
806 #[test]
807 fn test_execute_delete_action() {
808 let mut widget = ChatWidget::new();
809 widget.add_message(Message::user("Message 1"));
810 widget.add_message(Message::user("Message 2"));
811
812 widget.select_next();
813 assert_eq!(widget.messages.len(), 2);
814
815 let result = widget.execute_action(MessageAction::Delete);
816 assert!(result.is_ok());
817 assert_eq!(widget.messages.len(), 1);
818 }
819
820 #[test]
821 fn test_execute_edit_action() {
822 let mut widget = ChatWidget::new();
823 widget.add_message(Message::user("Original message"));
824 widget.select_next();
825
826 let result = widget.execute_action(MessageAction::Edit);
827 assert!(result.is_ok());
828 assert_eq!(widget.input, "Original message");
829 }
830
831 #[test]
832 fn test_streaming_message_animation() {
833 let mut msg = StreamingMessage::new();
834 msg.append("Hello");
835
836 assert_eq!(msg.animation_frame, 0);
838 msg.update_animation();
839 assert_eq!(msg.animation_frame, 1);
840 msg.update_animation();
841 assert_eq!(msg.animation_frame, 2);
842 msg.update_animation();
843 assert_eq!(msg.animation_frame, 3);
844 msg.update_animation();
845 assert_eq!(msg.animation_frame, 0); }
847
848 #[test]
849 fn test_streaming_message_cursor_animation() {
850 let mut msg = StreamingMessage::new();
851 msg.append("Test");
852
853 msg.animation_frame = 0;
855 assert_eq!(msg.display_text(), "Test_");
856
857 msg.animation_frame = 1;
858 assert_eq!(msg.display_text(), "Test_");
859
860 msg.animation_frame = 2;
862 assert_eq!(msg.display_text(), "Test ");
863
864 msg.animation_frame = 3;
865 assert_eq!(msg.display_text(), "Test ");
866 }
867
868 #[test]
869 fn test_streaming_message_token_count() {
870 let mut msg = StreamingMessage::new();
871 assert_eq!(msg.token_count, 0);
872
873 msg.append("Hello");
874 assert_eq!(msg.token_count, 1);
875
876 msg.append(" ");
877 assert_eq!(msg.token_count, 2);
878
879 msg.append("world");
880 assert_eq!(msg.token_count, 3);
881 }
882
883 #[test]
884 fn test_streaming_message_custom_cursor() {
885 let msg = StreamingMessage::new();
886 let display = msg.display_text_with_cursor("▌");
887 assert_eq!(display, "▌");
888
889 let mut msg = StreamingMessage::new();
890 msg.append("Loading");
891 let display = msg.display_text_with_cursor("▌");
892 assert_eq!(display, "Loading▌");
893
894 msg.finish();
895 let display = msg.display_text_with_cursor("▌");
896 assert_eq!(display, "Loading");
897 }
898
899 #[test]
900 fn test_chat_widget_start_streaming() {
901 let mut widget = ChatWidget::new();
902 assert!(!widget.is_streaming);
903 assert!(widget.streaming_message.is_none());
904
905 widget.start_streaming();
906 assert!(widget.is_streaming);
907 assert!(widget.streaming_message.is_some());
908 }
909
910 #[test]
911 fn test_chat_widget_append_token() {
912 let mut widget = ChatWidget::new();
913 widget.start_streaming();
914
915 widget.append_token("Hello");
916 widget.append_token(" ");
917 widget.append_token("world");
918
919 let display = widget.get_streaming_display();
920 assert!(display.is_some());
921 assert!(display.unwrap().contains("Hello world"));
922 }
923
924 #[test]
925 fn test_chat_widget_finish_streaming() {
926 let mut widget = ChatWidget::new();
927 widget.start_streaming();
928 widget.append_token("Test message");
929
930 let message = widget.finish_streaming();
931 assert!(message.is_some());
932 assert!(!widget.is_streaming);
933 assert!(widget.streaming_message.is_none());
934 assert_eq!(widget.messages.len(), 1);
935 assert_eq!(widget.messages[0].content, "Test message");
936 assert_eq!(widget.messages[0].author, MessageAuthor::Assistant);
937 }
938
939 #[test]
940 fn test_chat_widget_cancel_streaming() {
941 let mut widget = ChatWidget::new();
942 widget.start_streaming();
943 widget.append_token("Partial message");
944
945 widget.cancel_streaming();
946 assert!(!widget.is_streaming);
947 assert!(widget.streaming_message.is_none());
948 assert!(widget.messages.is_empty());
949 }
950
951 #[test]
952 fn test_chat_widget_update_streaming_animation() {
953 let mut widget = ChatWidget::new();
954 widget.start_streaming();
955 widget.append_token("Animating");
956
957 let initial_frame = widget.streaming_message.as_ref().unwrap().animation_frame;
958 widget.update_streaming_animation();
959 let new_frame = widget.streaming_message.as_ref().unwrap().animation_frame;
960
961 assert_ne!(initial_frame, new_frame);
962 }
963
964 #[test]
965 fn test_chat_widget_clear_with_streaming() {
966 let mut widget = ChatWidget::new();
967 widget.add_message(Message::user("Hello"));
968 widget.start_streaming();
969 widget.append_token("Response");
970
971 widget.clear();
972 assert!(widget.messages.is_empty());
973 assert!(!widget.is_streaming);
974 assert!(widget.streaming_message.is_none());
975 }
976
977 #[test]
978 fn test_streaming_message_len_and_empty() {
979 let mut msg = StreamingMessage::new();
980 assert!(msg.is_empty());
981 assert_eq!(msg.len(), 0);
982
983 msg.append("Hello");
984 assert!(!msg.is_empty());
985 assert_eq!(msg.len(), 5);
986
987 msg.append(" world");
988 assert_eq!(msg.len(), 11);
989 }
990
991 #[test]
992 fn test_streaming_message_is_complete() {
993 let mut msg = StreamingMessage::new();
994 assert!(!msg.is_complete());
995
996 msg.finish();
997 assert!(msg.is_complete());
998 }
999
1000 #[test]
1001 fn test_message_extract_code_blocks() {
1002 let msg =
1003 Message::assistant("Here's some code:\n```rust\nfn main() {}\n```\nAnd more text");
1004 let blocks = msg.extract_code_blocks();
1005 assert_eq!(blocks.len(), 1);
1006 assert!(blocks[0].contains("fn main()"));
1007 }
1008
1009 #[test]
1010 fn test_message_extract_multiple_code_blocks() {
1011 let msg = Message::assistant(
1012 "First:\n```rust\nfn foo() {}\n```\nSecond:\n```python\ndef bar(): pass\n```",
1013 );
1014 let blocks = msg.extract_code_blocks();
1015 assert_eq!(blocks.len(), 2);
1016 }
1017
1018 #[test]
1019 fn test_message_get_first_code_block() {
1020 let msg = Message::assistant("Code:\n```rust\nfn main() {}\n```");
1021 let block = msg.get_first_code_block();
1022 assert!(block.is_some());
1023 assert!(block.unwrap().contains("fn main()"));
1024 }
1025
1026 #[test]
1027 fn test_message_has_code_blocks() {
1028 let msg_with_code = Message::assistant("```rust\ncode\n```");
1029 assert!(msg_with_code.has_code_blocks());
1030
1031 let msg_without_code = Message::assistant("Just text");
1032 assert!(!msg_without_code.has_code_blocks());
1033 }
1034
1035 #[test]
1036 fn test_chat_widget_action_menu_toggle() {
1037 let mut widget = ChatWidget::new();
1038 widget.add_message(Message::user("Hello"));
1039 widget.select_next();
1040 widget.update_actions();
1041
1042 assert!(!widget.show_action_menu);
1043 widget.toggle_action_menu();
1044 assert!(widget.show_action_menu);
1045 assert_eq!(widget.selected_action, Some(0));
1046
1047 widget.toggle_action_menu();
1048 assert!(!widget.show_action_menu);
1049 }
1050
1051 #[test]
1052 fn test_chat_widget_action_menu_navigation() {
1053 let mut widget = ChatWidget::new();
1054 widget.add_message(Message::user("Hello"));
1055 widget.select_next();
1056 widget.update_actions();
1057 widget.toggle_action_menu();
1058
1059 widget.action_menu_down();
1060 assert_eq!(widget.selected_action, Some(1));
1061
1062 widget.action_menu_down();
1063 assert_eq!(widget.selected_action, Some(2));
1064
1065 widget.action_menu_up();
1066 assert_eq!(widget.selected_action, Some(1));
1067
1068 widget.action_menu_up();
1069 assert_eq!(widget.selected_action, Some(0));
1070 }
1071
1072 #[test]
1073 fn test_chat_widget_close_action_menu() {
1074 let mut widget = ChatWidget::new();
1075 widget.add_message(Message::user("Hello"));
1076 widget.select_next();
1077 widget.update_actions();
1078 widget.toggle_action_menu();
1079
1080 assert!(widget.show_action_menu);
1081 widget.close_action_menu();
1082 assert!(!widget.show_action_menu);
1083 assert!(widget.selected_action.is_none());
1084 }
1085
1086 #[test]
1087 fn test_chat_widget_get_selected_action() {
1088 let mut widget = ChatWidget::new();
1089 widget.add_message(Message::user("Hello"));
1090 widget.select_next();
1091 widget.update_actions();
1092 widget.toggle_action_menu();
1093
1094 let action = widget.get_selected_action();
1095 assert_eq!(action, Some(MessageAction::Copy));
1096 }
1097
1098 #[test]
1099 fn test_chat_widget_execute_copy_action_result() {
1100 let mut widget = ChatWidget::new();
1101 widget.add_message(Message::user("Hello"));
1102 widget.select_next();
1103
1104 let result = widget.execute_action(MessageAction::Copy);
1105 let _ = result;
1107 }
1108
1109 #[test]
1110 fn test_chat_widget_execute_delete_action_result() {
1111 let mut widget = ChatWidget::new();
1112 widget.add_message(Message::user("Message 1"));
1113 widget.add_message(Message::user("Message 2"));
1114
1115 widget.select_next();
1116 assert_eq!(widget.messages.len(), 2);
1117
1118 let result = widget.execute_action(MessageAction::Delete);
1119 assert!(result.is_ok());
1120 assert_eq!(widget.messages.len(), 1);
1121 }
1122
1123 #[test]
1124 fn test_chat_widget_execute_edit_action_result() {
1125 let mut widget = ChatWidget::new();
1126 widget.add_message(Message::user("Original message"));
1127 widget.select_next();
1128
1129 let result = widget.execute_action(MessageAction::Edit);
1130 assert!(result.is_ok());
1131 assert_eq!(widget.input, "Original message");
1132 }
1133
1134 #[test]
1135 fn test_chat_widget_execute_action_no_selection() {
1136 let mut widget = ChatWidget::new();
1137 widget.add_message(Message::user("Hello"));
1138
1139 let result = widget.execute_action(MessageAction::Copy);
1140 assert!(result.is_err());
1141 }
1142
1143 #[test]
1144 fn test_chat_widget_execute_action_by_shortcut() {
1145 let mut widget = ChatWidget::new();
1146 widget.add_message(Message::user("Hello"));
1147 widget.select_next();
1148 widget.update_actions();
1149
1150 let result = widget.execute_action_by_shortcut('c');
1151 let _ = result;
1153 }
1154
1155 #[test]
1156 fn test_chat_widget_execute_action_by_invalid_shortcut() {
1157 let mut widget = ChatWidget::new();
1158 widget.add_message(Message::user("Hello"));
1159 widget.select_next();
1160 widget.update_actions();
1161
1162 let result = widget.execute_action_by_shortcut('x');
1163 assert!(result.is_err());
1164 }
1165
1166 #[test]
1167 fn test_chat_widget_copy_feedback_visibility() {
1168 let mut widget = ChatWidget::new();
1169 assert!(!widget.is_copy_feedback_visible());
1170
1171 widget.add_message(Message::user("Hello"));
1172 widget.select_next();
1173 let _ = widget.execute_action(MessageAction::Copy);
1174
1175 assert!(widget.is_copy_feedback_visible());
1177 }
1178
1179 #[test]
1180 fn test_chat_widget_update_copy_feedback() {
1181 let mut widget = ChatWidget::new();
1182 widget.add_message(Message::user("Hello"));
1183 widget.select_next();
1184 let _ = widget.execute_action(MessageAction::Copy);
1185
1186 assert!(widget.is_copy_feedback_visible());
1187
1188 for _ in 0..100 {
1190 widget.update_copy_feedback();
1191 }
1192
1193 assert!(!widget.is_copy_feedback_visible());
1195 }
1196
1197 #[test]
1198 fn test_chat_widget_execute_copy_code_action() {
1199 let mut widget = ChatWidget::new();
1200 widget.add_message(Message::assistant("Code:\n```rust\nfn main() {}\n```"));
1201 widget.select_next();
1202 widget.update_actions();
1203
1204 assert!(widget.available_actions.contains(&MessageAction::CopyCode));
1205 let result = widget.execute_action(MessageAction::CopyCode);
1206 let _ = result;
1208 }
1209
1210 #[test]
1211 fn test_chat_widget_execute_copy_code_no_code() {
1212 let mut widget = ChatWidget::new();
1213 widget.add_message(Message::assistant("Just text, no code"));
1214 widget.select_next();
1215
1216 let result = widget.execute_action(MessageAction::CopyCode);
1217 assert!(result.is_err());
1218 }
1219
1220 #[test]
1221 fn test_chat_widget_execute_regenerate_action() {
1222 let mut widget = ChatWidget::new();
1223 widget.add_message(Message::assistant("Response"));
1224 widget.select_next();
1225 widget.update_actions();
1226
1227 assert!(widget
1228 .available_actions
1229 .contains(&MessageAction::Regenerate));
1230 let result = widget.execute_action(MessageAction::Regenerate);
1231 assert!(result.is_ok());
1232 }
1233
1234 #[test]
1235 fn test_chat_widget_execute_regenerate_user_message() {
1236 let mut widget = ChatWidget::new();
1237 widget.add_message(Message::user("Question"));
1238 widget.select_next();
1239
1240 let result = widget.execute_action(MessageAction::Regenerate);
1241 assert!(result.is_err());
1242 }
1243
1244 #[test]
1245 fn test_chat_widget_clear_with_copy_operation() {
1246 let mut widget = ChatWidget::new();
1247 widget.add_message(Message::user("Hello"));
1248 widget.select_next();
1249 let _ = widget.execute_action(MessageAction::Copy);
1250
1251 assert!(widget.copy_operation.is_some());
1252 widget.clear();
1253 assert!(widget.copy_operation.is_none());
1254 }
1255
1256 #[test]
1257 fn test_chat_widget_execute_selected_action() {
1258 let mut widget = ChatWidget::new();
1259 widget.add_message(Message::user("Message 1"));
1260 widget.add_message(Message::user("Message 2"));
1261 widget.select_next();
1262 widget.update_actions();
1263 widget.toggle_action_menu();
1264
1265 widget.action_menu_down();
1267 widget.action_menu_down();
1268 widget.action_menu_down();
1269
1270 let result = widget.execute_selected_action();
1271 assert!(result.is_ok());
1272 assert_eq!(widget.messages.len(), 1);
1273 }
1274
1275 #[test]
1276 fn test_chat_widget_action_menu_no_selection() {
1277 let mut widget = ChatWidget::new();
1278 widget.add_message(Message::user("Hello"));
1279
1280 widget.toggle_action_menu();
1282 assert!(!widget.show_action_menu);
1283 }
1284
1285 #[test]
1286 fn test_chat_widget_action_menu_no_actions() {
1287 let mut widget = ChatWidget::new();
1288 widget.add_message(Message::user("Hello"));
1289 widget.select_next();
1290 widget.toggle_action_menu();
1293 assert!(!widget.show_action_menu);
1294 }
1295}