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