use crate::types::Message; use textwrap::{wrap, Options}; /// Formats messages for display across different clients. #[derive(Debug, Clone)] pub struct MessageFormatter { wrap_width: usize, show_role_labels: bool, preserve_empty_lines: bool, } impl MessageFormatter { /// Create a new formatter pub fn new(wrap_width: usize, show_role_labels: bool) -> Self { Self { wrap_width: wrap_width.max(20), show_role_labels, preserve_empty_lines: true, } } /// Override whether empty lines should be preserved pub fn with_preserve_empty(mut self, preserve: bool) -> Self { self.preserve_empty_lines = preserve; self } /// Render a message to a list of visual lines ready for display pub fn format_message(&self, message: &Message) -> Vec { let mut lines = Vec::new(); let mut content = message.content.trim_end().to_string(); if content.is_empty() && self.preserve_empty_lines { content.push(' '); } let options = Options::new(self.wrap_width) .break_words(true) .word_separator(textwrap::WordSeparator::UnicodeBreakProperties); let wrapped = wrap(&content, &options); if self.show_role_labels { let label = format!("{}:", message.role.to_string().to_uppercase()); if let Some(first) = wrapped.first() { lines.push(format!("{label} {first}")); for line in wrapped.iter().skip(1) { lines.push(format!("{:width$} {line}", "", width = label.len())); } } else { lines.push(label); } } else { for line in wrapped { lines.push(line.into_owned()); } } lines } }