use super::layout::{
BottomPanelKind, resolve_bottom_panel_spec, split_input_and_bottom_panel_area,
};
use super::*;
use crate::config::constants::ui;
use crate::core_tui::app::session::transient::TransientSurface;
use crate::core_tui::session::render as core_render;
use crate::core_tui::session::{inline_list, list_panel, message_renderer};
impl Session {
pub fn render(&mut self, frame: &mut Frame<'_>) {
let Some(viewport) = self.core.begin_frame(frame) else {
return;
};
let mut metrics = self.core.measure_frame(viewport);
let local_agents_captures_input = self.inline_lists_visible()
&& matches!(
self.visible_bottom_docked_surface(),
Some(TransientSurface::LocalAgents)
);
let panel = resolve_bottom_panel_spec(
self,
viewport,
metrics.header_height,
if local_agents_captures_input {
0
} else {
metrics.input_core_height
},
);
if local_agents_captures_input {
metrics.input_core_height = 0;
}
let layout = self
.core
.build_frame_layout(viewport, metrics, panel.height);
self.core.set_modal_list_area(None);
let transcript_area = layout.main_area;
let (input_area, bottom_panel_area) = if matches!(panel.kind, BottomPanelKind::LocalAgents)
{
(
Rect::new(
layout.input_area.x,
layout.input_area.y,
layout.input_area.width,
0,
),
Some(layout.input_area),
)
} else {
split_input_and_bottom_panel_area(layout.input_area, panel.height)
};
self.core.set_bottom_panel_area(bottom_panel_area);
self.core.render_base_frame(frame, &layout, transcript_area);
self.core.render_input(frame, input_area);
if let Some(panel_area) = bottom_panel_area {
match panel.kind {
BottomPanelKind::AgentPalette => {
render::render_agent_palette(self, frame, panel_area);
}
BottomPanelKind::FilePalette => {
render::render_file_palette(self, frame, panel_area);
}
BottomPanelKind::HistoryPicker => {
render::render_history_picker(self, frame, panel_area);
}
BottomPanelKind::SlashPalette => {
slash::render_slash_palette(self, frame, panel_area);
}
BottomPanelKind::TaskPanel => {
render_task_panel(self, frame, panel_area);
}
BottomPanelKind::LocalAgents => {
render::render_local_agents(self, frame, panel_area);
}
BottomPanelKind::None => {
frame.render_widget(Clear, panel_area);
}
}
}
if self.has_active_overlay() {
core_render::render_modal(
self,
frame,
core_render::floating_modal_area(layout.viewport),
);
}
if self.diff_preview_state().is_some() {
diff_preview::render_diff_preview(self, frame, layout.viewport);
}
if let Some(mut state) = self.transcript_review_state.take() {
let width = transcript_review::review_content_width(layout.viewport);
let height = layout.viewport.height.saturating_sub(4);
state.refresh(self, width, height);
transcript_review::render_transcript_review(self, frame, layout.viewport, &state);
self.transcript_review_state = Some(state);
}
self.core.finalize_mouse_selection(frame, layout.viewport);
}
#[allow(dead_code)]
pub(crate) fn render_message_spans(&self, index: usize) -> Vec<Span<'static>> {
let Some(line) = self.core.lines.get(index) else {
return vec![Span::raw(String::new())];
};
message_renderer::render_message_spans(
line,
&self.core.theme,
&self.core.labels,
|kind| self.core.prefix_text(kind),
|line| self.core.prefix_style(line),
|kind| self.core.text_fallback(kind),
)
}
}
fn render_task_panel(session: &mut Session, frame: &mut Frame<'_>, area: Rect) {
if area.width == 0 || area.height == 0 {
return;
}
let rows = if session.task_panel_lines.is_empty() {
vec![(
inline_list::InlineListRow::single(
ui::PLAN_STATUS_EMPTY.to_string().into(),
session.core.header_secondary_style(),
),
1,
)]
} else {
session
.task_panel_lines
.iter()
.map(|line| {
(
inline_list::InlineListRow::single(
line.clone().into(),
session.core.header_secondary_style(),
),
1,
)
})
.collect()
};
let item_count = session.task_panel_lines.len();
let sections = list_panel::SharedListPanelSections {
header: vec![Line::from(vec![Span::styled(
ui::PLAN_BLOCK_TITLE.to_string(),
session.core.section_title_style(),
)])],
info: vec![Line::from(format!(
"{} item{}",
item_count,
if item_count == 1 { "" } else { "s" }
))],
search: None,
};
let styles = list_panel::SharedListPanelStyles {
base_style: session.core.styles.default_style(),
selected_style: Some(session.core.styles.modal_list_highlight_style()),
text_style: session.core.header_secondary_style(),
divider_style: None,
};
let mut model = list_panel::StaticRowsListPanelModel {
rows,
selected: None,
offset: 0,
visible_rows: area.height as usize,
};
list_panel::render_shared_list_panel(frame, area, sections, styles, &mut model);
}