Skip to main content

vtcode_tui/core_tui/app/session/
impl_render.rs

1use super::layout::{
2    BottomPanelKind, resolve_bottom_panel_spec, split_input_and_bottom_panel_area,
3};
4use super::*;
5use crate::config::constants::ui;
6use crate::core_tui::session::render as core_render;
7use crate::core_tui::session::{inline_list, list_panel, message_renderer};
8
9impl Session {
10    pub fn render(&mut self, frame: &mut Frame<'_>) {
11        let Some(viewport) = self.core.begin_frame(frame) else {
12            return;
13        };
14        let metrics = self.core.measure_frame(viewport);
15        let panel = resolve_bottom_panel_spec(
16            self,
17            viewport,
18            metrics.header_height,
19            metrics.input_core_height,
20        );
21        let layout = self
22            .core
23            .build_frame_layout(viewport, metrics, panel.height);
24        self.core.set_modal_list_area(None);
25        let transcript_area = layout.main_area;
26        let (input_area, bottom_panel_area) =
27            split_input_and_bottom_panel_area(layout.input_area, panel.height);
28        self.core.set_bottom_panel_area(bottom_panel_area);
29        self.core.render_base_frame(frame, &layout, transcript_area);
30        self.core.render_input(frame, input_area);
31        if let Some(panel_area) = bottom_panel_area {
32            match panel.kind {
33                BottomPanelKind::FilePalette => {
34                    render::render_file_palette(self, frame, panel_area);
35                }
36                BottomPanelKind::HistoryPicker => {
37                    render::render_history_picker(self, frame, panel_area);
38                }
39                BottomPanelKind::SlashPalette => {
40                    slash::render_slash_palette(self, frame, panel_area);
41                }
42                BottomPanelKind::TaskPanel => {
43                    render_task_panel(self, frame, panel_area);
44                }
45                BottomPanelKind::None => {
46                    frame.render_widget(Clear, panel_area);
47                }
48            }
49        }
50
51        if self.has_active_overlay() {
52            core_render::render_modal(
53                self,
54                frame,
55                core_render::floating_modal_area(layout.viewport),
56            );
57        }
58
59        if self.diff_preview_state().is_some() {
60            diff_preview::render_diff_preview(self, frame, layout.viewport);
61        }
62        self.core.finalize_mouse_selection(frame, layout.viewport);
63    }
64
65    #[allow(dead_code)]
66    pub(crate) fn render_message_spans(&self, index: usize) -> Vec<Span<'static>> {
67        let Some(line) = self.core.lines.get(index) else {
68            return vec![Span::raw(String::new())];
69        };
70        message_renderer::render_message_spans(
71            line,
72            &self.core.theme,
73            &self.core.labels,
74            |kind| self.core.prefix_text(kind),
75            |line| self.core.prefix_style(line),
76            |kind| self.core.text_fallback(kind),
77        )
78    }
79}
80
81fn render_task_panel(session: &mut Session, frame: &mut Frame<'_>, area: Rect) {
82    if area.width == 0 || area.height == 0 {
83        return;
84    }
85
86    let rows = if session.task_panel_lines.is_empty() {
87        vec![(
88            inline_list::InlineListRow::single(
89                ui::PLAN_STATUS_EMPTY.to_string().into(),
90                session.core.header_secondary_style(),
91            ),
92            1,
93        )]
94    } else {
95        session
96            .task_panel_lines
97            .iter()
98            .map(|line| {
99                (
100                    inline_list::InlineListRow::single(
101                        line.clone().into(),
102                        session.core.header_secondary_style(),
103                    ),
104                    1,
105                )
106            })
107            .collect()
108    };
109    let item_count = session.task_panel_lines.len();
110    let sections = list_panel::SharedListPanelSections {
111        header: vec![Line::from(vec![Span::styled(
112            ui::PLAN_BLOCK_TITLE.to_string(),
113            session.core.section_title_style(),
114        )])],
115        info: vec![Line::from(format!(
116            "{} item{}",
117            item_count,
118            if item_count == 1 { "" } else { "s" }
119        ))],
120        search: None,
121    };
122    let styles = list_panel::SharedListPanelStyles {
123        base_style: session.core.styles.default_style(),
124        selected_style: Some(session.core.styles.modal_list_highlight_style()),
125        text_style: session.core.header_secondary_style(),
126    };
127    let mut model = list_panel::StaticRowsListPanelModel {
128        rows,
129        selected: None,
130        offset: 0,
131        visible_rows: area.height as usize,
132    };
133    list_panel::render_shared_list_panel(frame, area, sections, styles, &mut model);
134}