vtcode_commons/
formatting.rs1pub fn format_size(size: u64) -> String {
5 const KB: u64 = 1024;
6 const MB: u64 = KB * 1024;
7 const GB: u64 = MB * 1024;
8
9 if size >= GB {
10 format!("{:.1}GB", size as f64 / GB as f64)
11 } else if size >= MB {
12 format!("{:.1}MB", size as f64 / MB as f64)
13 } else if size >= KB {
14 format!("{:.1}KB", size as f64 / KB as f64)
15 } else {
16 format!("{}B", size)
17 }
18}
19
20pub fn indent_block(text: &str, indent: &str) -> String {
22 if indent.is_empty() || text.is_empty() {
23 return text.to_string();
24 }
25 let mut indented = String::with_capacity(text.len() + indent.len() * text.lines().count());
26 for (idx, line) in text.split('\n').enumerate() {
27 if idx > 0 {
28 indented.push('\n');
29 }
30 if !line.is_empty() {
31 indented.push_str(indent);
32 }
33 indented.push_str(line);
34 }
35 indented
36}
37
38pub fn truncate_text(text: &str, max_len: usize, ellipsis: &str) -> String {
40 if text.chars().count() <= max_len {
41 return text.to_string();
42 }
43
44 let mut truncated = text.chars().take(max_len).collect::<String>();
45 truncated.push_str(ellipsis);
46 truncated
47}
48
49pub fn truncate_byte_budget(text: &str, max_bytes: usize, suffix: &str) -> String {
53 if text.len() <= max_bytes {
54 return text.to_string();
55 }
56 let mut end = max_bytes.min(text.len());
57 while end > 0 && !text.is_char_boundary(end) {
58 end -= 1;
59 }
60 format!("{}{suffix}", &text[..end])
61}
62
63#[inline]
71pub fn collapse_whitespace(text: &str) -> String {
72 let mut result = String::with_capacity(text.len());
73 let mut pending_space = false;
74 for ch in text.chars() {
75 if ch.is_whitespace() {
76 pending_space = true;
77 } else {
78 if pending_space && !result.is_empty() {
79 result.push(' ');
80 }
81 result.push(ch);
82 pending_space = false;
83 }
84 }
85 result
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn truncate_byte_budget_ascii() {
94 assert_eq!(truncate_byte_budget("hello world", 5, "..."), "hello...");
95 assert_eq!(truncate_byte_budget("hi", 10, "..."), "hi");
96 }
97
98 #[test]
99 fn truncate_byte_budget_cjk_no_panic() {
100 let jp = "こんにちは";
102 assert_eq!(truncate_byte_budget(jp, 5, "…"), "こ…");
104 assert_eq!(truncate_byte_budget(jp, 6, "…"), "こん…");
106 }
107
108 #[test]
109 fn truncate_byte_budget_mixed_ascii_cjk() {
110 let mixed = "AB日本語CD";
111 assert_eq!(truncate_byte_budget(mixed, 4, ".."), "AB.."); assert_eq!(truncate_byte_budget(mixed, 5, ".."), "AB日.."); }
115
116 #[test]
117 fn truncate_byte_budget_emoji() {
118 let emoji = "👋🌍"; assert_eq!(truncate_byte_budget(emoji, 5, "!"), "👋!");
120 }
121
122 #[test]
123 fn truncate_byte_budget_zero() {
124 assert_eq!(truncate_byte_budget("abc", 0, "..."), "...");
125 }
126
127 #[test]
128 fn truncate_text_counts_chars_not_bytes() {
129 let jp = "あいうえお"; assert_eq!(truncate_text(jp, 3, "…"), "あいう…");
131 assert_eq!(truncate_text(jp, 5, "…"), "あいうえお");
132 }
133}