use crate::ui::events::sanitize_output;
use crate::ui::renderer::Renderer;
use crate::ui::theme;
pub(crate) fn with_queue(s: String, n: usize) -> String {
if n == 0 { s } else { format!("{} q:{}", s, n) }
}
pub(crate) fn strip_leading_system_reminder(content: &str) -> &str {
let trimmed = content.trim_start();
let Some(rest) = trimmed.strip_prefix("<system-reminder>") else {
return content;
};
let Some(end) = rest.find("</system-reminder>") else {
return content;
};
let after = &rest[end + "</system-reminder>".len()..];
after.trim_start_matches(['\n', '\r', ' ', '\t'])
}
#[cfg(test)]
mod strip_system_reminder_tests {
use super::strip_leading_system_reminder;
#[test]
fn passes_plain_text_through() {
assert_eq!(strip_leading_system_reminder("hello"), "hello");
}
#[test]
fn strips_block_and_trailing_blank_lines() {
let input = "<system-reminder>\nTask 1 done\n</system-reminder>\n\nwhat's next?";
assert_eq!(strip_leading_system_reminder(input), "what's next?");
}
#[test]
fn does_not_strip_mid_message_reminder() {
let input = "see <system-reminder>nope</system-reminder>";
assert_eq!(strip_leading_system_reminder(input), input);
}
#[test]
fn handles_leading_whitespace_before_reminder() {
let input = " \n<system-reminder>x</system-reminder>\nhi";
assert_eq!(strip_leading_system_reminder(input), "hi");
}
#[test]
fn missing_close_tag_leaves_input_alone() {
let input = "<system-reminder>oops";
assert_eq!(strip_leading_system_reminder(input), input);
}
}
fn write_prefixed_lines(
renderer: &mut Renderer,
prefix: &str,
color: crossterm::style::Color,
text: &str,
) -> std::io::Result<()> {
let cont_indent = " ".repeat(prefix.chars().count());
let mut prefix_emitted = false;
for line in text.lines() {
let safe = sanitize_output(line);
if safe.is_empty() {
renderer.write_line("", color)?;
continue;
}
let formatted = if !prefix_emitted {
prefix_emitted = true;
format!("{}{}", prefix, safe)
} else {
format!("{}{}", cont_indent, safe)
};
renderer.write_line(&formatted, color)?;
}
if !prefix_emitted {
renderer.write_line(prefix, color)?;
}
Ok(())
}
pub(crate) fn write_user_lines(renderer: &mut Renderer, text: &str) -> std::io::Result<()> {
write_prefixed_lines(renderer, "<you> ", theme::user(), text)
}
pub(crate) fn write_critic_lines(renderer: &mut Renderer, text: &str) -> std::io::Result<()> {
write_prefixed_lines(renderer, "<critic> ", theme::critic(), text)
}
pub(crate) fn write_system_lines(renderer: &mut Renderer, text: &str) -> std::io::Result<()> {
write_prefixed_lines(renderer, "<system> ", theme::warn(), text)
}
pub(crate) fn sanitize_single_line(s: &str, max_chars: usize) -> String {
let mut out = String::with_capacity(s.len().min(max_chars));
let mut count = 0;
for c in s.chars() {
if count >= max_chars {
out.push('…');
return out;
}
let replacement = match c {
'\n' | '\r' | '\t' => ' ',
c if c.is_control() => continue,
'\u{001B}' => continue,
c => c,
};
out.push(replacement);
count += 1;
}
out
}