(&self, frame: &mut Frame, area: Rect, theme: &Theme)
| 58 | } |
| 59 | |
| 60 | pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { |
| 61 | let mut lines: Vec<Line> = Vec::new(); |
| 62 | |
| 63 | for part in &self.parts { |
| 64 | match part { |
| 65 | MessagePart::Text { content } => { |
| 66 | lines.extend(self.render_text(content, theme)); |
| 67 | } |
| 68 | MessagePart::ToolCall { |
| 69 | name, |
| 70 | status, |
| 71 | result, |
| 72 | arguments, |
| 73 | .. |
| 74 | } => { |
| 75 | lines.extend(self.render_tool_call( |
| 76 | name, |
| 77 | arguments, |
| 78 | status, |
| 79 | result.as_deref(), |
| 80 | theme, |
| 81 | )); |
| 82 | } |
| 83 | MessagePart::Reasoning { |
| 84 | content, |
| 85 | duration_ms, |
| 86 | } => { |
| 87 | if self.show_thinking { |
| 88 | lines.extend(self.render_reasoning(content, *duration_ms, theme)); |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // Error display (non-abort errors) |
| 95 | if let Some(err) = &self.error { |
| 96 | lines.push(Line::from(Span::styled( |
| 97 | format!("Error: {}", err), |
| 98 | Style::default().fg(theme.error), |
| 99 | ))); |
| 100 | } |
| 101 | |
| 102 | // Completion footer for assistant messages |
| 103 | if matches!(self.role, MessageRole::Assistant) { |
| 104 | if let Some(model) = &self.model_id { |
| 105 | let mut footer_spans = vec![Span::styled( |
| 106 | model.as_str(), |
| 107 | Style::default().fg(theme.text_muted), |
| 108 | )]; |
| 109 | if let Some(ms) = self.duration_ms { |
| 110 | let duration_text = if ms < 1000 { |
| 111 | format!(" {}ms", ms) |
| 112 | } else { |
| 113 | format!(" {:.1}s", ms as f64 / 1000.0) |
| 114 | }; |
| 115 | footer_spans.push(Span::styled( |
| 116 | duration_text, |
| 117 | Style::default().fg(theme.text_muted), |
nothing calls this directly
no test coverage detected