use std::borrow::Cow;
use ansi_to_tui::IntoText;
use ratatui::text::{Line, Span};
use termimad::MadSkin;
pub fn text_to_lines(text: &str) -> Vec<Line<'static>> {
if text.is_empty() {
return Vec::new();
}
let text = sanitize_tui_block_text(text);
let text = text.as_ref();
if text.is_empty() {
return Vec::new();
}
let ansi_text = if contains_ansi(text) {
text.to_string()
} else {
let skin = MadSkin::default();
skin.term_text(text).to_string()
};
match ansi_text.as_str().into_text() {
Ok(parsed_text) => {
parsed_text
.lines
.into_iter()
.map(|line| {
let owned_spans: Vec<Span<'static>> = line
.spans
.into_iter()
.map(|span| Span::styled(span.content.into_owned(), span.style))
.collect();
Line::from(owned_spans)
})
.collect()
}
Err(_) => {
text.split('\n')
.map(|line| Line::from(line.to_string()))
.collect()
}
}
}
#[inline]
pub fn contains_ansi(text: &str) -> bool {
text.contains("\x1b[")
}
pub fn sanitize_tui_block_text(text: &str) -> Cow<'_, str> {
let has_cr = text.contains('\r');
let has_other_ctrl = text
.chars()
.any(|c| matches!(c, '\u{0007}' | '\u{0008}' | '\u{000b}' | '\u{000c}'));
if !has_cr && !has_other_ctrl {
return Cow::Borrowed(text);
}
let mut s = if has_cr {
text.replace("\r\n", "\n").replace('\r', "\n")
} else {
text.to_string()
};
if has_other_ctrl {
s.retain(|c| !matches!(c, '\u{0007}' | '\u{0008}' | '\u{000b}' | '\u{000c}'));
}
Cow::Owned(s)
}
pub fn truncate(s: &str, max_len: usize) -> String {
if s.chars().count() <= max_len {
s.to_string()
} else {
let byte_idx = s
.char_indices()
.nth(max_len)
.map(|(idx, _)| idx)
.unwrap_or(s.len());
format!("{}...", &s[..byte_idx])
}
}
pub fn sanitize_tui_inline_text(text: &str) -> String {
let mut s = text.replace("\r\n", " ").replace(['\r', '\n'], " ");
s.retain(|c| !matches!(c, '\u{0007}' | '\u{0008}' | '\u{000b}' | '\u{000c}'));
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_to_lines_empty() {
assert!(text_to_lines("").is_empty());
}
#[test]
fn test_text_to_lines_plain() {
let lines = text_to_lines("hello world");
assert!(!lines.is_empty());
}
#[test]
fn test_text_to_lines_multiline() {
let lines = text_to_lines("line1\nline2\nline3");
assert!(lines.len() >= 3);
}
#[test]
fn test_text_to_lines_ansi() {
let ansi_text = "\x1b[31mred text\x1b[0m";
let lines = text_to_lines(ansi_text);
assert!(!lines.is_empty());
}
#[test]
fn test_contains_ansi() {
assert!(!contains_ansi("plain text"));
assert!(contains_ansi("\x1b[31mred\x1b[0m"));
}
#[test]
fn test_sanitize_block_text_no_changes() {
let text = "normal text\nwith newlines";
let result = sanitize_tui_block_text(text);
assert_eq!(result, text);
assert!(matches!(result, Cow::Borrowed(_)));
}
#[test]
fn test_sanitize_block_text_cr() {
let text = "line1\r\nline2\rline3";
let result = sanitize_tui_block_text(text);
assert_eq!(result, "line1\nline2\nline3");
}
#[test]
fn test_sanitize_block_text_bell() {
let text = "text\u{0007}with bell";
let result = sanitize_tui_block_text(text);
assert_eq!(result, "textwith bell");
}
#[test]
fn test_sanitize_inline_text() {
let text = "line1\nline2\rline3";
let result = sanitize_tui_inline_text(text);
assert_eq!(result, "line1 line2 line3");
}
}