vtcode_tui/core_tui/app/session/
impl_render.rs1use 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}