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
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}