terminal_width/
terminal_width.rs1use east_asian_width::east_asian_width;
7
8fn terminal_width(text: &str) -> usize {
10 text.chars()
11 .map(|c| east_asian_width(c as u32).as_usize())
12 .sum()
13}
14
15fn terminal_width_with_config(text: &str, ambiguous_as_wide: bool) -> usize {
17 text.chars()
18 .map(|c| east_asian_width((c as u32, ambiguous_as_wide)).as_usize())
19 .sum()
20}
21
22fn pad_to_width(text: &str, target_width: usize) -> String {
24 let current_width = terminal_width(text);
25 if current_width >= target_width {
26 text.to_string()
27 } else {
28 let padding = " ".repeat(target_width - current_width);
29 format!("{}{}", text, padding)
30 }
31}
32
33fn truncate_to_width(text: &str, max_width: usize) -> String {
35 let mut result = String::new();
36 let mut current_width = 0;
37
38 for ch in text.chars() {
39 let char_width = east_asian_width(ch as u32).as_usize();
40 if current_width + char_width > max_width {
41 break;
42 }
43 result.push(ch);
44 current_width += char_width;
45 }
46
47 result
48}
49
50fn main() {
51 let examples = vec![
53 "Hello World", "こんにちは", "你好世界", "Hello 世界", "Café naïve résumé", "αβγδε", "Abc123", ];
61
62 println!("Terminal Width Examples");
63 println!("====================");
64 println!();
65
66 println!("Basic Width Calculation:");
68 for text in &examples {
69 let width = terminal_width(text);
70 println!(" {:20} → {} columns", format!("\"{}\"", text), width);
71 }
72 println!();
73
74 println!("Ambiguous Character Handling:");
76 let ambiguous_text = "αβγ→←";
77 println!(" Text: \"{}\"", ambiguous_text);
78 println!(
79 " Narrow: {} columns",
80 terminal_width_with_config(ambiguous_text, false)
81 );
82 println!(
83 " Wide: {} columns",
84 terminal_width_with_config(ambiguous_text, true)
85 );
86 println!();
87
88 println!("Padding to Fixed Width (20 columns):");
90 for text in &examples {
91 let padded = pad_to_width(text, 20);
92 println!(" |{}|", padded);
93 }
94 println!();
95
96 println!("Truncation to Fixed Width (10 columns):");
98 for text in &examples {
99 let truncated = truncate_to_width(text, 10);
100 let width = terminal_width(&truncated);
101 println!(
102 " {:20} → \"{}\" ({} columns)",
103 format!("\"{}\"", text),
104 truncated,
105 width
106 );
107 }
108 println!();
109
110 println!("Table Formatting Example:");
112 let data = vec![
113 ("Name", "Language", "Greeting"),
114 ("Alice", "English", "Hello"),
115 ("田中", "日本語", "こんにちは"),
116 ("李明", "中文", "你好"),
117 ("김철수", "한국어", "안녕하세요"),
118 ];
119
120 let col_widths = [12, 10, 15];
122
123 for (i, row) in data.iter().enumerate() {
124 if i == 1 {
125 println!(" {}", "-".repeat(col_widths.iter().sum::<usize>() + 6));
127 }
128
129 let formatted_row = format!(
130 " {} | {} | {}",
131 pad_to_width(row.0, col_widths[0]),
132 pad_to_width(row.1, col_widths[1]),
133 pad_to_width(row.2, col_widths[2])
134 );
135 println!("{}", formatted_row);
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_terminal_width() {
145 assert_eq!(terminal_width("Hello"), 5);
146 assert_eq!(terminal_width("こんにちは"), 10); assert_eq!(terminal_width("Hello世界"), 9); }
149
150 #[test]
151 fn test_padding() {
152 assert_eq!(pad_to_width("Hi", 5), "Hi ");
153 assert_eq!(pad_to_width("こんにちは", 10), "こんにちは");
154 assert_eq!(pad_to_width("Hi", 2), "Hi");
155 }
156
157 #[test]
158 fn test_truncation() {
159 assert_eq!(truncate_to_width("Hello World", 5), "Hello");
160 assert_eq!(truncate_to_width("こんにちは", 6), "こんに");
161 assert_eq!(truncate_to_width("Hello世界", 7), "Hello世");
162 }
163}