Skip to main content

vtcode_tui/core_tui/session/
impl_render.rs

1use super::*;
2
3impl Session {
4    pub fn render(&mut self, frame: &mut Frame<'_>) {
5        let viewport = frame.area();
6        if viewport.height == 0 || viewport.width == 0 {
7            return;
8        }
9
10        // Clear entire frame if modal was just closed to remove artifacts
11        if self.needs_full_clear {
12            frame.render_widget(Clear, viewport);
13            self.needs_full_clear = false;
14        }
15
16        // Calculate layout constraints
17        let header_lines = self.header_lines();
18        let header_height = self.header_height_from_lines(viewport.width, &header_lines);
19        if header_height != self.header_rows {
20            self.header_rows = header_height;
21            self.recalculate_transcript_rows();
22        }
23
24        let mode = self.resolved_layout_mode(viewport);
25        // Always reserve 1 row for the inline status line to prevent layout
26        // jumping when status content appears/disappears during task execution.
27        let status_height = if viewport.width > 0 && !mode.show_footer() {
28            1
29        } else {
30            0
31        };
32        let inner_width = viewport.width.saturating_sub(2);
33        let desired_lines = self.desired_input_lines(inner_width);
34        let block_height = Self::input_block_height_for_lines(desired_lines);
35        let input_height = block_height.saturating_add(status_height);
36        self.apply_input_height(input_height);
37
38        let mut constraints = vec![Constraint::Length(header_height), Constraint::Min(1)];
39        constraints.push(Constraint::Length(input_height));
40
41        let segments = Layout::vertical(constraints).split(viewport);
42
43        let header_area = segments[0];
44        let main_area = segments[1];
45        let input_index = segments.len().saturating_sub(1);
46        let input_area = segments[input_index];
47
48        let _available_width = main_area.width;
49        let _horizontal_minimum = ui::INLINE_CONTENT_MIN_WIDTH + ui::INLINE_NAVIGATION_MIN_WIDTH;
50
51        let transcript_area = main_area;
52        let navigation_area = Rect::new(main_area.x, main_area.y, 0, 0); // No navigation area since timeline pane is removed
53
54        // Use SessionWidget for buffer-based rendering (header, transcript, overlays)
55        SessionWidget::new(self)
56            .header_lines(header_lines.clone())
57            .header_area(header_area)
58            .transcript_area(transcript_area)
59            .navigation_area(navigation_area) // Pass empty navigation area
60            .render(viewport, frame.buffer_mut());
61
62        // Handle frame-based rendering for components that need it
63        // Note: header, transcript, and overlays are handled by SessionWidget
64        // Timeline pane has been removed, so no navigation rendering
65        self.render_input(frame, input_area);
66        render::render_modal(self, frame, viewport);
67        slash::render_slash_palette(self, frame, viewport);
68        render::render_file_palette(self, frame, viewport);
69
70        // Render diff preview modal if active
71        if self.diff_preview.is_some() {
72            diff_preview::render_diff_preview(self, frame, viewport);
73        }
74
75        // Apply mouse text selection highlight
76        if self.mouse_selection.has_selection || self.mouse_selection.is_selecting {
77            self.mouse_selection
78                .apply_highlight(frame.buffer_mut(), viewport);
79
80            // Copy to clipboard once when selection is finalized
81            if self.mouse_selection.needs_copy() {
82                let text = self
83                    .mouse_selection
84                    .extract_text(frame.buffer_mut(), viewport);
85                if !text.is_empty() {
86                    MouseSelectionState::copy_to_clipboard(&text);
87                }
88                self.mouse_selection.mark_copied();
89            }
90        }
91    }
92
93    #[allow(dead_code)]
94    pub(crate) fn render_message_spans(&self, index: usize) -> Vec<Span<'static>> {
95        let Some(line) = self.lines.get(index) else {
96            return vec![Span::raw(String::new())];
97        };
98        message_renderer::render_message_spans(
99            line,
100            &self.theme,
101            &self.labels,
102            |kind| self.prefix_text(kind),
103            |line| self.prefix_style(line),
104            |kind| self.text_fallback(kind),
105        )
106    }
107}