string-width 0.1.0

Accurate Unicode string width calculation for terminal applications, handling emoji, East Asian characters, combining marks, and ANSI escape sequences
Documentation
/// CLI tool example for measuring string widths
///
/// Run with: cargo run --example cli_tool -- "Hello 👋 World"
/// Or: echo "Hello 👋 World" | cargo run --example cli_tool
use std::env;
use std::io::{self, Read};
use string_width::{
    AmbiguousWidthTreatment, DisplayWidth, StringWidthOptions, string_width,
    string_width_with_options,
};

fn main() {
    let args: Vec<String> = env::args().collect();

    let input = if args.len() > 1 {
        // Use command line argument
        args[1..].join(" ")
    } else {
        // Read from stdin
        let mut buffer = String::new();
        match io::stdin().read_to_string(&mut buffer) {
            Ok(_) => buffer.trim_end().to_string(),
            Err(e) => {
                eprintln!("Error reading from stdin: {}", e);
                std::process::exit(1);
            }
        }
    };

    if input.is_empty() {
        eprintln!("Usage: {} <text> or echo <text> | {}", args[0], args[0]);
        eprintln!("       Measures the display width of Unicode text");
        std::process::exit(1);
    }

    println!("Text analysis for: '{}'", input);
    println!("{}", "".repeat(50));

    // Basic width
    let width = input.display_width();
    println!("Display width: {} characters", width);

    // Character count vs display width
    let char_count = input.chars().count();
    let byte_count = input.len();
    println!("Character count: {}", char_count);
    println!("Byte count: {}", byte_count);

    if width != char_count {
        println!("Note: Display width differs from character count");
        if width > char_count {
            println!("  → Contains wide characters (CJK, emoji)");
        } else {
            println!("  → Contains zero-width or combining characters");
        }
    }

    // Detailed analysis
    println!("\nDetailed breakdown:");
    for (i, ch) in input.chars().enumerate() {
        let ch_width = string_width(ch.to_string().as_str());
        let code_point = ch as u32;

        let char_type = match ch_width {
            0 => "zero-width",
            1 => "narrow",
            2 => "wide",
            _ => "unknown",
        };

        println!(
            "  {}: '{}' (U+{:04X}) - {} width",
            i + 1,
            ch,
            code_point,
            char_type
        );
    }

    // Different width calculation options
    println!("\nWidth with different options:");

    let narrow_ambiguous = StringWidthOptions {
        count_ansi: false,
        ambiguous_width: AmbiguousWidthTreatment::Narrow,
    };
    println!(
        "  Ambiguous as narrow: {}",
        string_width_with_options(input.as_str(), narrow_ambiguous)
    );

    let wide_ambiguous = StringWidthOptions {
        count_ansi: false,
        ambiguous_width: AmbiguousWidthTreatment::Wide,
    };
    println!(
        "  Ambiguous as wide: {}",
        string_width_with_options(input.as_str(), wide_ambiguous)
    );

    let count_ansi = StringWidthOptions {
        count_ansi: true,
        ambiguous_width: AmbiguousWidthTreatment::Narrow,
    };
    println!(
        "  Counting ANSI codes: {}",
        string_width_with_options(input.as_str(), count_ansi)
    );

    // Terminal formatting example
    println!("\nTerminal formatting examples:");
    let box_width = 40;
    let content_width = input.display_width();

    if content_width <= box_width - 4 {
        let padding = box_width - content_width - 4;
        let left_pad = padding / 2;
        let right_pad = padding - left_pad;

        println!("{}", "".repeat(box_width - 2));
        println!(
            "{}{}{}",
            " ".repeat(left_pad + 1),
            input,
            " ".repeat(right_pad + 1)
        );
        println!("{}", "".repeat(box_width - 2));
    } else {
        println!(
            "Text too wide for box (width: {}, box: {})",
            content_width,
            box_width - 4
        );
    }
}