pub fn sanitize_terminal_text(input: &str) -> String {
use regex::Regex;
use std::sync::OnceLock;
static OSC_SEQUENCE: OnceLock<Regex> = OnceLock::new();
static CSI_SEQUENCE: OnceLock<Regex> = OnceLock::new();
static ESC_SEQUENCE: OnceLock<Regex> = OnceLock::new();
let osc_sequence =
OSC_SEQUENCE.get_or_init(|| Regex::new(r"\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)").unwrap());
let csi_sequence = CSI_SEQUENCE.get_or_init(|| Regex::new(r"\x1B\[[0-?]*[ -/]*[@-~]").unwrap());
let esc_sequence = ESC_SEQUENCE.get_or_init(|| Regex::new(r"\x1B[@-Z\\-_]").unwrap());
let without_osc = osc_sequence.replace_all(input, "");
let without_csi = csi_sequence.replace_all(&without_osc, "");
let without_esc = esc_sequence.replace_all(&without_csi, "");
without_esc
.chars()
.filter(|ch| !ch.is_control() || matches!(ch, '\n' | '\t'))
.collect()
}
pub fn truncate(value: &str, max_len: Option<usize>) -> String {
let sanitized = sanitize_terminal_text(value);
let Some(max_len) = max_len else {
return sanitized;
};
if max_len == 0 {
return String::new();
}
let char_count = sanitized.chars().count();
if char_count <= max_len {
return sanitized;
}
if max_len <= 3 {
return sanitized.chars().take(max_len).collect();
}
let truncated: String = sanitized.chars().take(max_len - 3).collect();
format!("{}...", truncated)
}
pub fn is_uuid(value: &str) -> bool {
value.len() == 36 && value.matches("-").count() == 4
}
pub fn strip_markdown(input: &str) -> String {
use regex::Regex;
use std::sync::OnceLock;
static PATTERNS: OnceLock<Vec<(Regex, &str)>> = OnceLock::new();
let patterns = PATTERNS.get_or_init(|| {
vec![
(Regex::new(r"!\[([^\]]*)\]\([^)]+\)").unwrap(), "$1"),
(Regex::new(r"\[([^\]]+)\]\([^)]+\)").unwrap(), "$1"),
(Regex::new(r"\*{3}([^*]+)\*{3}").unwrap(), "$1"),
(Regex::new(r"_{3}([^_]+)_{3}").unwrap(), "$1"),
(Regex::new(r"\*{2}([^*]+)\*{2}").unwrap(), "$1"),
(Regex::new(r"_{2}([^_]+)_{2}").unwrap(), "$1"),
(Regex::new(r"\*([^*]+)\*").unwrap(), "$1"),
(
Regex::new(r"(?:^|[\s(])_([^_]+)_(?:[\s).,;:!?]|$)").unwrap(),
"$1",
),
(Regex::new(r"~~([^~]+)~~").unwrap(), "$1"),
(Regex::new(r"`([^`]+)`").unwrap(), "$1"),
(Regex::new(r"(?m)^#{1,6}\s+").unwrap(), ""),
(Regex::new(r"(?m)^[-*_]{3,}\s*$").unwrap(), ""),
(Regex::new(r"(?m)^>\s?").unwrap(), ""),
(Regex::new(r"(?m)^[\s]*[-*+]\s").unwrap(), " "),
(Regex::new(r"(?m)^[\s]*\d+\.\s").unwrap(), " "),
]
});
let mut result = input.to_string();
static CODE_FENCE: OnceLock<Regex> = OnceLock::new();
let code_fence = CODE_FENCE.get_or_init(|| Regex::new(r"(?m)^```\w*\s*$").unwrap());
result = code_fence.replace_all(&result, "").to_string();
for (pattern, replacement) in patterns {
result = pattern.replace_all(&result, *replacement).to_string();
}
static MULTI_BLANK: OnceLock<Regex> = OnceLock::new();
let multi_blank = MULTI_BLANK.get_or_init(|| Regex::new(r"\n{3,}").unwrap());
result = multi_blank.replace_all(&result, "\n\n").to_string();
sanitize_terminal_text(result.trim())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_truncate_none() {
assert_eq!(truncate("hello world", None), "hello world");
}
#[test]
fn test_truncate_zero() {
assert_eq!(truncate("hello", Some(0)), "");
}
#[test]
fn test_truncate_short_string() {
assert_eq!(truncate("hi", Some(10)), "hi");
}
#[test]
fn test_truncate_exact_length() {
assert_eq!(truncate("hello", Some(5)), "hello");
}
#[test]
fn test_truncate_with_ellipsis() {
assert_eq!(truncate("hello world", Some(8)), "hello...");
}
#[test]
fn test_truncate_unicode() {
assert_eq!(truncate("こんにちは世界", Some(5)), "こん...");
assert_eq!(truncate("hello世界", Some(8)), "hello世界");
assert_eq!(truncate("hello世界", Some(6)), "hel...");
}
#[test]
fn test_is_uuid_valid() {
assert!(is_uuid("550e8400-e29b-41d4-a716-446655440000"));
assert!(is_uuid("00000000-0000-0000-0000-000000000000"));
}
#[test]
fn test_is_uuid_invalid() {
assert!(!is_uuid("not-a-uuid"));
assert!(!is_uuid("550e8400e29b41d4a716446655440000")); assert!(!is_uuid("550e8400-e29b-41d4-a716")); assert!(!is_uuid("")); }
#[test]
fn test_strip_markdown_headers() {
assert_eq!(strip_markdown("# Title"), "Title");
assert_eq!(strip_markdown("## Subtitle"), "Subtitle");
assert_eq!(strip_markdown("### Deep"), "Deep");
}
#[test]
fn test_strip_markdown_bold_italic() {
assert_eq!(strip_markdown("**bold**"), "bold");
assert_eq!(strip_markdown("__bold__"), "bold");
assert_eq!(strip_markdown("*italic*"), "italic");
assert_eq!(strip_markdown("***both***"), "both");
}
#[test]
fn test_strip_markdown_links() {
assert_eq!(
strip_markdown("[click here](https://example.com)"),
"click here"
);
assert_eq!(strip_markdown(""), "alt text");
}
#[test]
fn test_strip_markdown_code() {
assert_eq!(strip_markdown("`inline code`"), "inline code");
assert_eq!(strip_markdown("```rust\nlet x = 1;\n```"), "let x = 1;");
}
#[test]
fn test_strip_markdown_strikethrough() {
assert_eq!(strip_markdown("~~deleted~~"), "deleted");
}
#[test]
fn test_strip_markdown_blockquote() {
assert_eq!(strip_markdown("> quoted text"), "quoted text");
}
#[test]
fn test_strip_markdown_lists() {
assert_eq!(strip_markdown("- item one"), "item one");
assert_eq!(strip_markdown("* item two"), "item two");
assert_eq!(strip_markdown("1. numbered"), "numbered");
assert_eq!(strip_markdown("text\n- item"), "text\n item");
}
#[test]
fn test_strip_markdown_horizontal_rule() {
assert_eq!(strip_markdown("above\n---\nbelow"), "above\n\nbelow");
}
#[test]
fn test_strip_markdown_plain_text() {
assert_eq!(strip_markdown("just plain text"), "just plain text");
}
#[test]
fn test_strip_markdown_collapses_blank_lines() {
assert_eq!(strip_markdown("a\n\n\n\nb"), "a\n\nb");
}
#[test]
fn test_truncate_strips_ansi_before_counting() {
assert_eq!(truncate("\u{1b}[31mhello\u{1b}[0m", Some(5)), "hello");
}
#[test]
fn test_strip_markdown_removes_terminal_control_sequences() {
assert_eq!(
strip_markdown("**hello** \u{1b}]52;c;ZXZpbA==\u{7} world"),
"hello world"
);
}
#[test]
fn test_strip_markdown_removes_embedded_control_characters() {
assert_eq!(strip_markdown("ok\u{8}\u{0c}then"), "okthen");
}
}