1use unicode_width::UnicodeWidthStr;
6
7pub fn expand_tabs(s: &str, tab_width: usize) -> String {
18 let mut result = String::with_capacity(s.len());
19 let mut column = 0;
20
21 for ch in s.chars() {
22 if ch == '\t' {
23 let spaces = tab_width - (column % tab_width);
25 result.push_str(&" ".repeat(spaces));
26 column += spaces;
27 } else if !ch.is_control() {
28 result.push(ch);
30 column += ch.to_string().width();
31 }
32 }
34
35 result
36}
37
38pub fn truncate_chars(s: &str, max_chars: usize) -> String {
53 if max_chars == 0 {
54 return String::new();
55 }
56
57 let char_count = s.chars().count();
58 if char_count <= max_chars {
59 s.to_string()
60 } else if max_chars <= 3 {
61 s.chars().take(max_chars).collect()
62 } else {
63 format!("{}...", s.chars().take(max_chars - 3).collect::<String>())
64 }
65}
66
67pub fn truncate_width(s: &str, max_width: usize) -> String {
78 if max_width == 0 {
79 return String::new();
80 }
81
82 let s_width = s.width();
83 if s_width <= max_width {
84 return s.to_string();
85 }
86
87 if max_width <= 1 {
88 return ".".to_string();
89 }
90
91 let target_width = max_width - 1;
93
94 let mut result = String::new();
95 let mut current_width = 0;
96
97 for ch in s.chars() {
98 let ch_width = ch.to_string().width();
99 if current_width + ch_width > target_width {
100 break;
101 }
102 result.push(ch);
103 current_width += ch_width;
104 }
105
106 result.push('…');
107 result
108}
109
110#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_truncate_chars_no_truncation() {
120 assert_eq!(truncate_chars("hello", 10), "hello");
121 assert_eq!(truncate_chars("hello", 5), "hello");
122 }
123
124 #[test]
125 fn test_truncate_chars_with_truncation() {
126 assert_eq!(truncate_chars("hello world", 8), "hello...");
127 assert_eq!(truncate_chars("hello world", 6), "hel...");
128 }
129
130 #[test]
131 fn test_truncate_chars_edge_cases() {
132 assert_eq!(truncate_chars("hello", 0), "");
133 assert_eq!(truncate_chars("hello", 3), "hel");
134 assert_eq!(truncate_chars("hello", 2), "he");
135 }
136
137 #[test]
138 fn test_truncate_width_no_truncation() {
139 assert_eq!(truncate_width("hello", 10), "hello");
140 assert_eq!(truncate_width("hello", 5), "hello");
141 }
142
143 #[test]
144 fn test_truncate_width_with_truncation() {
145 assert_eq!(truncate_width("hello world", 8), "hello w…");
146 assert_eq!(truncate_width("hello world", 6), "hello…");
147 }
148
149 #[test]
150 fn test_truncate_width_edge_cases() {
151 assert_eq!(truncate_width("hello", 0), "");
152 assert_eq!(truncate_width("hello", 1), ".");
153 assert_eq!(truncate_width("hello", 2), "h…");
154 }
155
156 #[test]
157 fn test_truncate_width_unicode() {
158 let cjk = "你好世界"; assert_eq!(cjk.width(), 8);
161
162 let result = truncate_width(cjk, 6);
163 assert!(result.width() <= 6);
164 }
165}