1use comfy_table::presets::NOTHING;
4use comfy_table::{ContentArrangement, Table};
5
6const MIN_WIDTH: u16 = 20;
7
8#[must_use]
9pub fn terminal_width() -> Option<u16> {
11 Table::new().width().or_else(|| {
12 std::env::var("COLUMNS")
13 .ok()?
14 .parse::<u16>()
15 .ok()
16 .filter(|width| *width >= MIN_WIDTH)
17 })
18}
19
20pub fn constrain(table: &mut Table) {
22 if let Some(width) = terminal_width() {
23 table.set_width(width);
24 }
25}
26
27#[must_use]
28pub fn wrap(value: &str) -> String {
30 terminal_width().map_or_else(|| value.to_string(), |width| wrap_to(value, width))
31}
32
33fn wrap_to(value: &str, width: u16) -> String {
34 let mut table = Table::new();
35 table
36 .load_style(NOTHING)
37 .set_content_arrangement(ContentArrangement::Dynamic)
38 .set_width(width)
39 .add_row([value]);
40 if let Some(column) = table.column_mut(0) {
41 column.set_padding((0, 0));
42 }
43 table
44 .to_string()
45 .lines()
46 .map(str::trim_end)
47 .collect::<Vec<_>>()
48 .join("\n")
49}
50
51pub fn push_indented(output: &mut String, value: &str, indentation: u16) {
53 let prefix = " ".repeat(usize::from(indentation));
54 let wrapped = terminal_width().map_or_else(
55 || value.to_string(),
56 |width| wrap_to(value, width.saturating_sub(indentation)),
57 );
58 for line in wrapped.lines() {
59 output.push_str(&prefix);
60 output.push_str(line);
61 output.push('\n');
62 }
63}
64
65pub fn push_line(output: &mut String, value: &str) {
67 output.push_str(&wrap(value));
68 if !output.ends_with('\n') {
69 output.push('\n');
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::{push_indented, push_line};
76
77 #[test]
78 fn push_line_terminates() {
79 let mut out = String::new();
80 push_line(&mut out, "hello");
81 assert!(out.ends_with('\n'));
82 assert!(out.contains("hello"));
83 }
84
85 #[test]
86 fn indent_prefixes() {
87 let mut out = String::new();
88 push_indented(&mut out, "x", 2);
89 assert!(out.starts_with(" x"));
90 }
91}