use ratatui::{style::Modifier, text::Span};
use super::super::style::{ratatui_pty_style_from_inline, ratatui_style_from_inline};
use super::super::types::{InlineMessageKind, InlineTextStyle, InlineTheme};
use super::message::{MessageLabels, MessageLine};
use crate::config::constants::ui;
#[allow(dead_code)]
pub(crate) fn render_message_spans(
line: &MessageLine,
theme: &InlineTheme,
labels: &MessageLabels,
prefix_text_fn: impl Fn(InlineMessageKind) -> Option<String>,
prefix_style_fn: impl Fn(&MessageLine) -> InlineTextStyle,
text_fallback_fn: impl Fn(InlineMessageKind) -> Option<anstyle::Color>,
) -> Vec<Span<'static>> {
let mut spans = Vec::with_capacity(line.segments.len() + 2);
if line.kind == InlineMessageKind::Agent {
spans.extend(agent_prefix_spans(line, theme, labels, &prefix_style_fn));
} else if let Some(prefix) = prefix_text_fn(line.kind) {
let style = prefix_style_fn(line);
spans.push(Span::styled(
prefix,
ratatui_style_from_inline(&style, theme.foreground),
));
}
if line.kind == InlineMessageKind::Agent {
spans.push(Span::raw(ui::INLINE_AGENT_MESSAGE_LEFT_PADDING));
}
if line.segments.is_empty() {
if spans.is_empty() {
spans.push(Span::raw(""));
}
return spans;
}
if line.kind == InlineMessageKind::Tool {
let tool_spans = render_tool_segments(line, theme);
if tool_spans.is_empty() {
spans.push(Span::raw(""));
} else {
spans.extend(tool_spans);
}
return spans;
}
if line.kind == InlineMessageKind::Pty {
let pty_fallback = text_fallback_fn(InlineMessageKind::Pty).or(theme.foreground);
for segment in &line.segments {
let style = ratatui_pty_style_from_inline(&segment.style, pty_fallback);
spans.push(Span::styled(segment.text.clone(), style));
}
if !spans.is_empty() {
return spans;
}
}
let fallback = text_fallback_fn(line.kind).or(theme.foreground);
for segment in &line.segments {
let mut style = ratatui_style_from_inline(&segment.style, fallback);
if line.kind == InlineMessageKind::User {
style = style.add_modifier(Modifier::BOLD);
}
spans.push(Span::styled(segment.text.clone(), style));
}
if spans.is_empty() {
spans.push(Span::raw(""));
}
spans
}
#[allow(dead_code)]
pub(crate) fn agent_prefix_spans(
line: &MessageLine,
theme: &InlineTheme,
labels: &MessageLabels,
prefix_style_fn: &impl Fn(&MessageLine) -> InlineTextStyle,
) -> Vec<Span<'static>> {
let mut spans = Vec::with_capacity(3);
let prefix_style = ratatui_style_from_inline(&prefix_style_fn(line), theme.foreground);
let has_label = labels.agent.as_ref().is_some_and(|label| !label.is_empty());
let prefix_has_trailing_space = ui::INLINE_AGENT_QUOTE_PREFIX
.chars()
.last()
.is_some_and(|ch| ch.is_whitespace());
if !ui::INLINE_AGENT_QUOTE_PREFIX.is_empty() {
spans.push(Span::styled(ui::INLINE_AGENT_QUOTE_PREFIX, prefix_style));
if has_label && !prefix_has_trailing_space {
spans.push(Span::styled(" ", prefix_style));
}
}
if let Some(label) = &labels.agent
&& !label.is_empty()
{
spans.push(Span::styled(label.clone(), prefix_style));
}
spans
}
#[allow(dead_code)]
pub(crate) fn render_tool_segments(line: &MessageLine, theme: &InlineTheme) -> Vec<Span<'static>> {
let mut spans = Vec::with_capacity(line.segments.len());
for segment in &line.segments {
let style = ratatui_style_from_inline(&segment.style, theme.foreground);
spans.push(Span::styled(segment.text.clone(), style));
}
spans
}
#[allow(dead_code)]
fn render_styled_action_text(
spans: &mut Vec<Span<'static>>,
action: &str,
body_style: &InlineTextStyle,
theme: &InlineTheme,
) {
for (i, word) in action.split_whitespace().enumerate() {
if i > 0 {
spans.push(Span::raw(" "));
}
spans.push(Span::styled(
word.to_owned(),
ratatui_style_from_inline(body_style, theme.foreground),
));
}
}
#[allow(dead_code)]
fn strip_tool_status_prefix(text: &str) -> &str {
let trimmed = text.trim_start();
const STATUS_ICONS: [&str; 4] = ["✓", "✗", "~", "✕"];
for icon in STATUS_ICONS {
if let Some(rest) = trimmed.strip_prefix(icon) {
return rest.trim_start();
}
}
text
}