Render interface
(&mut self, frame: &mut Frame, chat_state: &ChatState)
| 1 | impl ChatView { |
| 2 | /// Render interface |
| 3 | pub fn render(&mut self, frame: &mut Frame, chat_state: &ChatState) { |
| 4 | let size = frame.area(); |
| 5 | frame.render_widget( |
| 6 | Block::default().style(Style::default().bg(self.theme.background)), |
| 7 | size, |
| 8 | ); |
| 9 | |
| 10 | // Dynamic input area height: 2 (borders) + visible content lines, capped at 8+2=10 |
| 11 | let max_input_content_lines: u16 = 8; |
| 12 | let input_inner_width = size.width.saturating_sub(2); // subtract left+right borders |
| 13 | let total_visual_lines = self.text_input.visual_line_count(input_inner_width) as u16; |
| 14 | let content_lines = total_visual_lines.max(1).min(max_input_content_lines); |
| 15 | let input_height = content_lines + 2; // +2 for top/bottom borders |
| 16 | |
| 17 | // Calculate shortcuts area height based on content |
| 18 | let shortcuts_height = Self::calculate_shortcuts_height(size.width, chat_state, self.browse_mode); |
| 19 | // Status area can grow for long status messages to avoid horizontal truncation. |
| 20 | let raw_status_height = |
| 21 | Self::calculate_status_height(size.width, chat_state, self.status.as_deref()); |
| 22 | // Keep a minimal conversation viewport while allowing status to expand when possible. |
| 23 | let max_status_height = size |
| 24 | .height |
| 25 | .saturating_sub(3 + input_height + shortcuts_height + 3) |
| 26 | .max(1); |
| 27 | let status_height = raw_status_height.min(max_status_height); |
| 28 | |
| 29 | // Main layout: header + content + status bar + input + shortcuts |
| 30 | let chunks = Layout::default() |
| 31 | .direction(Direction::Vertical) |
| 32 | .constraints([ |
| 33 | Constraint::Length(3), // header |
| 34 | Constraint::Min(10), // messages area |
| 35 | Constraint::Length(status_height), // status bar (dynamic) |
| 36 | Constraint::Length(input_height), // input area (dynamic) |
| 37 | Constraint::Length(shortcuts_height), // shortcuts hint (dynamic) |
| 38 | ]) |
| 39 | .split(size); |
| 40 | |
| 41 | // Render each part |
| 42 | self.render_header(frame, chunks[0], chat_state); |
| 43 | self.render_messages(frame, chunks[1], chat_state); |
| 44 | self.render_status_bar(frame, chunks[2], chat_state); |
| 45 | self.render_input(frame, chunks[3], chat_state); |
| 46 | self.render_command_menu(frame, chunks[1]); |
| 47 | self.render_model_selector(frame, chunks[1]); |
| 48 | self.render_agent_selector(frame, chunks[1]); |
| 49 | self.render_session_selector(frame, chunks[1]); |
| 50 | self.render_skill_selector(frame, chunks[1]); |
| 51 | self.render_subagent_selector(frame, chunks[1]); |
| 52 | self.render_mcp_selector(frame, chunks[1]); |
| 53 | self.render_mcp_add_dialog(frame, chunks[1]); |
| 54 | self.render_provider_selector(frame, chunks[1]); |
| 55 | self.render_model_config_form(frame, chunks[1]); |
| 56 | self.render_theme_selector(frame, chunks[1]); |
| 57 | self.render_shortcuts(frame, chunks[4], chat_state); |
| 58 | |
| 59 | // Render permission overlay on top of messages area if active (highest priority) |
| 60 | if let Some(ref prompt) = chat_state.permission_prompt { |
nothing calls this directly
no test coverage detected