use std::sync::LazyLock;
use regex::Regex;
use crate::textproc::{Token, process_tokens, push_original_token};
static DOT_RE: LazyLock<Regex> = lazy_regex!(r"\.{3,}", "ellipsis pattern regex should compile");
#[must_use]
pub fn replace_ellipsis(lines: &[String]) -> Vec<String> {
process_tokens(lines, |tok, out| match tok {
Token::Text(t) => {
if !DOT_RE.is_match(t) {
out.push_str(t);
return;
}
let replaced = DOT_RE.replace_all(t, |caps: ®ex::Captures<'_>| {
let len = caps[0].len();
let ellipses = "…".repeat(len / 3);
let leftover = ".".repeat(len % 3);
format!("{ellipses}{leftover}")
});
out.push_str(&replaced);
}
_ => push_original_token(&tok, out),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn replaces_simple_text() {
let input = vec!["wait...".to_string()];
let expected = vec!["wait…".to_string()];
assert_eq!(replace_ellipsis(&input), expected);
}
#[test]
fn ignores_code_spans() {
let input = vec!["a `b...` c".to_string()];
let expected = input.clone();
assert_eq!(replace_ellipsis(&input), expected);
}
#[test]
fn ignores_fenced_blocks() {
let input = vec!["```".to_string(), "...".to_string(), "```".to_string()];
let expected = input.clone();
assert_eq!(replace_ellipsis(&input), expected);
}
#[test]
fn replaces_long_sequences() {
let input = vec![".... ..... ...... .......".to_string()];
let expected = vec!["…. ….. …… …….".to_string()];
assert_eq!(replace_ellipsis(&input), expected);
}
#[test]
fn handles_empty_input() {
let input: Vec<String> = Vec::new();
let expected: Vec<String> = Vec::new();
assert_eq!(replace_ellipsis(&input), expected);
}
#[test]
fn handles_multiple_fenced_blocks() {
let input = vec![
"text...".to_string(),
"```".to_string(),
"code...".to_string(),
"```".to_string(),
"more text...".to_string(),
];
let expected = vec![
"text…".to_string(),
"```".to_string(),
"code...".to_string(),
"```".to_string(),
"more text…".to_string(),
];
assert_eq!(replace_ellipsis(&input), expected);
}
}