1pub const TRUNCATION_MARKER: &str = "[... truncated]";
12
13#[must_use]
19pub fn truncate_middle(text: &str, max_chars: usize) -> (String, bool) {
20 if text.char_indices().nth(max_chars).is_none() {
21 return (text.to_string(), false);
22 }
23 let total = text.chars().count();
24 let head = max_chars / 2;
25 let tail = max_chars - head;
26 let byte_at = |chars: usize| {
27 text.char_indices()
28 .nth(chars)
29 .map_or(text.len(), |(i, _)| i)
30 };
31 let (head_end, tail_start) = (byte_at(head), byte_at(total - tail));
32 let cut = format!(
33 "{}\n\n{TRUNCATION_MARKER}\n\n{}",
34 &text[..head_end],
35 &text[tail_start..]
36 );
37 (cut, true)
38}
39
40#[cfg(test)]
41#[allow(clippy::unwrap_used, clippy::expect_used)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn short_text_is_unchanged() {
47 let (out, truncated) = truncate_middle("short", 1000);
48 assert_eq!(out, "short");
49 assert!(!truncated);
50 }
51
52 #[test]
53 fn truncates_on_a_char_boundary() {
54 let text = "é".repeat(100);
55 let (full, _) = truncate_middle(&text, usize::MAX);
56 let (out, truncated) = truncate_middle(&text, 20);
57 assert!(truncated);
58 let (head, tail) = out
59 .split_once(&format!("\n\n{TRUNCATION_MARKER}\n\n"))
60 .unwrap();
61 assert_eq!(head.chars().count(), 10);
62 assert_eq!(tail.chars().count(), 10);
63 assert!(full.starts_with(head));
64 assert!(full.ends_with(tail));
65 }
66
67 #[test]
68 fn handles_a_one_char_cap() {
69 let (out, truncated) = truncate_middle("hello world", 1);
71 assert!(truncated);
72 assert_eq!(out, format!("\n\n{TRUNCATION_MARKER}\n\nd"));
73 }
74
75 #[test]
76 fn exactly_at_the_cap_is_not_truncated() {
77 let (out, truncated) = truncate_middle("hello", 5);
78 assert_eq!(out, "hello");
79 assert!(!truncated);
80 }
81}