pub fn get_terminal_width() -> usize {
std::env::var("COLUMNS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(80)
}
pub fn max_usage_width<'a>(items: impl Iterator<Item = &'a str>) -> usize {
items.map(visible_width).max().unwrap_or(0)
}
pub fn visible_width(s: &str) -> usize {
s.chars().count()
}
pub fn wrap_text(text: &str, width: usize) -> Vec<String> {
if width == 0 {
return vec![text.to_string()];
}
let mut lines = Vec::new();
for paragraph in text.split('\n') {
if paragraph.is_empty() {
lines.push(String::new());
continue;
}
let mut current_line = String::new();
let mut current_width = 0;
for word in paragraph.split_whitespace() {
let word_width = visible_width(word);
if current_width > 0 && current_width + 1 + word_width > width {
lines.push(current_line);
current_line = String::new();
current_width = 0;
}
if current_width > 0 {
current_line.push(' ');
current_width += 1;
}
current_line.push_str(word);
current_width += word_width;
}
if !current_line.is_empty() {
lines.push(current_line);
}
}
if lines.is_empty() {
vec![String::new()]
} else {
lines
}
}
pub fn render_help_text(
help: &str,
terminal_width: usize,
usage_col_width: usize,
) -> (String, bool) {
if help.contains('\n') {
return (String::new(), false);
}
let indent = 2;
let gap = 2;
let first_line_prefix_width = indent + usage_col_width + gap;
let continuation_indent = first_line_prefix_width;
let available_width = terminal_width.saturating_sub(first_line_prefix_width);
if available_width < 10 {
return (String::new(), false);
}
let wrapped_lines = wrap_text(help, available_width);
if wrapped_lines.is_empty() || (wrapped_lines.len() == 1 && wrapped_lines[0].is_empty()) {
return (String::new(), false);
}
let is_multiline = wrapped_lines.len() > 1;
let mut result = wrapped_lines[0].clone();
for line in &wrapped_lines[1..] {
result.push('\n');
result.push_str(&" ".repeat(continuation_indent));
result.push_str(line);
}
(result, is_multiline)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_visible_width() {
assert_eq!(visible_width("hello"), 5);
assert_eq!(visible_width(""), 0);
assert_eq!(visible_width("hello world"), 11);
}
#[test]
fn test_wrap_text_short() {
let text = "short";
let wrapped = wrap_text(text, 20);
assert_eq!(wrapped, vec!["short"]);
}
#[test]
fn test_wrap_text_long() {
let text = "this is a very long text that should wrap";
let wrapped = wrap_text(text, 20);
assert!(wrapped.len() > 1);
for line in &wrapped {
assert!(visible_width(line) <= 20);
}
}
#[test]
fn test_wrap_text_with_newlines() {
let text = "line one\nline two";
let wrapped = wrap_text(text, 20);
assert_eq!(wrapped, vec!["line one", "line two"]);
}
#[test]
fn test_render_help_text_short() {
let help = "Short help";
let (rendered, is_multiline) = render_help_text(help, 80, 20);
assert_eq!(rendered, "Short help");
assert!(!is_multiline);
}
#[test]
fn test_render_help_text_long() {
let help = "This is a very long help text that should wrap to multiple lines when rendered";
let (rendered, is_multiline) = render_help_text(help, 60, 20);
assert!(is_multiline);
assert!(rendered.contains('\n'));
}
#[test]
fn test_render_help_text_with_newlines() {
let help = "Line one\nLine two";
let (rendered, is_multiline) = render_help_text(help, 80, 20);
assert_eq!(rendered, "");
assert!(!is_multiline);
}
}