use super::*;
use crate::tui::Entry;
use ratatui::text::Span;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::tui) enum TrailingBlank {
Include,
Omit,
}
impl TrailingBlank {
fn is_included(self) -> bool {
match self {
Self::Include => true,
Self::Omit => false,
}
}
}
pub(in crate::tui) fn entry_lines(
entry: &Entry,
width: usize,
max_tool_output_lines: usize,
max_image_height: u16,
) -> Vec<Line<'static>> {
render_entry(entry, width, max_tool_output_lines, max_image_height).lines
}
pub(in crate::tui) fn render_entry(
entry: &Entry,
width: usize,
max_tool_output_lines: usize,
max_image_height: u16,
) -> RenderedEntry {
render_entry_with_options(
entry,
width,
max_tool_output_lines,
max_image_height,
TrailingBlank::Include,
)
}
pub(in crate::tui) fn render_entry_with_options(
entry: &Entry,
width: usize,
max_tool_output_lines: usize,
max_image_height: u16,
trailing_blank: TrailingBlank,
) -> RenderedEntry {
let inner_width = padded_content_width(width);
let RenderedEntry {
mut lines,
code_blocks,
image_sources,
image_rows,
..
} = match entry {
Entry::Assistant(assistant) => render_markdown_entry_with_summary(
&assistant.text,
width,
inner_width,
render_assistant_content,
assistant.worked_for.map(crate::tui::goal::worked_summary),
),
Entry::Reasoning(reasoning) => render_markdown_entry_with_summary(
&reasoning.text,
width,
inner_width,
render_reasoning_content,
reasoning.thought_for.map(crate::tui::goal::thought_summary),
),
_ => {
let mut lines = Vec::new();
render_non_assistant_entry(&mut lines, entry, inner_width, max_tool_output_lines);
RenderedEntry {
lines,
..RenderedEntry::default()
}
}
};
let image_placement = reserve_entry_image_rows(&mut lines, entry, width, max_image_height);
let spacer_style = match entry {
crate::tui::Entry::Tool(_) => Theme::tool_card_padding(),
crate::tui::Entry::User(_) => Style::default(),
crate::tui::Entry::Assistant(_)
| crate::tui::Entry::Reasoning(_)
| crate::tui::Entry::Notice(_)
| crate::tui::Entry::RuntimeInfo(_)
| crate::tui::Entry::Changelog(_)
| crate::tui::Entry::Error(_) => lines
.first()
.and_then(|line| line.spans.first())
.map(|span| chrome_edge_style(span.style))
.unwrap_or_default(),
};
let mut padded = Vec::with_capacity(lines.len() + usize::from(trailing_blank.is_included()));
padded.extend(lines.into_iter().map(pad_display_line));
if trailing_blank.is_included() {
padded.push(styled_blank_line(width, spacer_style));
}
RenderedEntry {
lines: padded,
code_blocks,
image_placement,
image_sources,
image_rows,
}
}
fn render_markdown_entry_with_summary(
text: &str,
width: usize,
inner_width: usize,
render_content: fn(&str, usize) -> RenderedEntry,
summary: Option<String>,
) -> RenderedEntry {
let mut rendered = if text.is_empty() {
RenderedEntry::default()
} else {
render_content(text, width)
};
if let Some(summary) = summary {
let summary_style = Theme::dim().add_modifier(Modifier::DIM);
if !rendered.lines.is_empty() {
rendered
.lines
.push(Line::from(Span::styled(String::new(), summary_style)));
}
push_wrapped_text(
&mut rendered.lines,
&summary,
inner_width,
summary_style,
LineFill::Natural,
);
}
rendered
}
pub(in crate::tui) fn apply_markdown_images(
rendered: &mut RenderedEntry,
images: &[(usize, FeedImage)],
width: usize,
max_image_height: u16,
) {
if images.is_empty() {
return;
}
for block in &mut rendered.code_blocks {
let original_top_line = block.top_line;
let preceding_image_rows = images
.iter()
.filter_map(|(source_index, image)| {
rendered
.image_rows
.get(*source_index)
.filter(|&&row| row < original_top_line)
.map(|_| {
image
.height_for_width(width, max_image_height)
.saturating_sub(1)
})
})
.sum::<usize>();
block.top_line = block.top_line.saturating_add(preceding_image_rows);
}
if let Some(placements) = reserve_markdown_image_rows(
&mut rendered.lines,
&rendered.image_rows,
images,
width,
max_image_height,
) {
rendered.image_placement = Some(placements);
}
}