extern crate alloc;
use alloc::string::{ String, ToString };
use alloc::format;
use super::{ Segment, parse_segments };
pub fn visual_len( text : &str ) -> usize
{
parse_segments( text )
.iter()
.filter_map( | seg | match seg
{
Segment::Text( t ) => Some( t.chars().count() ),
Segment::Ansi( _ ) => None,
})
.sum()
}
#[ cfg( feature = "ansi_unicode" ) ]
pub fn visual_len_unicode( text : &str ) -> usize
{
use unicode_segmentation::UnicodeSegmentation;
parse_segments( text )
.iter()
.filter_map( | seg | match seg
{
Segment::Text( t ) => Some( t.graphemes( true ).count() ),
Segment::Ansi( _ ) => None,
})
.sum()
}
pub fn pad_to_width( text : &str, target_width : usize, align_right : bool ) -> String
{
use unicode_width::UnicodeWidthStr;
let visible_text : String = parse_segments( text )
.iter()
.filter_map( | seg | match seg
{
Segment::Text( t ) => Some( *t ),
Segment::Ansi( _ ) => None,
})
.collect();
let current_width = visible_text.width();
if current_width >= target_width
{
return text.to_string();
}
let padding = target_width - current_width;
let spaces : String = " ".repeat( padding );
if align_right
{
format!( "{}{}", spaces, text )
}
else
{
format!( "{}{}", text, spaces )
}
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn visual_len_empty()
{
assert_eq!( visual_len( "" ), 0 );
}
#[ test ]
fn visual_len_plain_text()
{
assert_eq!( visual_len( "hello" ), 5 );
assert_eq!( visual_len( "hello world" ), 11 );
}
#[ test ]
fn visual_len_ansi_only()
{
assert_eq!( visual_len( "\x1b[31m" ), 0 );
assert_eq!( visual_len( "\x1b[0m" ), 0 );
assert_eq!( visual_len( "\x1b[1;31;44m" ), 0 );
}
#[ test ]
fn visual_len_ansi_with_text()
{
assert_eq!( visual_len( "\x1b[31mred\x1b[0m" ), 3 );
assert_eq!( visual_len( "\x1b[1;31mbold red\x1b[0m" ), 8 );
}
#[ test ]
fn visual_len_multiple_ansi()
{
assert_eq!( visual_len( "\x1b[1m\x1b[31mtest\x1b[0m" ), 4 );
}
#[ test ]
fn visual_len_unicode_codepoints()
{
assert_eq!( visual_len( "ζ₯ζ¬θͺ" ), 3 );
assert_eq!( visual_len( "π" ), 1 );
}
#[ test ]
fn pad_left_align()
{
assert_eq!( pad_to_width( "hi", 5, false ), "hi " );
assert_eq!( pad_to_width( "test", 10, false ), "test " );
}
#[ test ]
fn pad_right_align()
{
assert_eq!( pad_to_width( "hi", 5, true ), " hi" );
assert_eq!( pad_to_width( "test", 10, true ), " test" );
}
#[ test ]
fn pad_no_change_when_equal_or_larger()
{
assert_eq!( pad_to_width( "hello", 5, false ), "hello" );
assert_eq!( pad_to_width( "hello world", 5, false ), "hello world" );
}
#[ test ]
fn pad_with_ansi()
{
let result = pad_to_width( "\x1b[31mhi\x1b[0m", 5, false );
assert_eq!( result, "\x1b[31mhi\x1b[0m " );
let result = pad_to_width( "\x1b[31mhi\x1b[0m", 5, true );
assert_eq!( result, " \x1b[31mhi\x1b[0m" );
}
#[ test ]
fn pad_empty_string()
{
assert_eq!( pad_to_width( "", 3, false ), " " );
assert_eq!( pad_to_width( "", 3, true ), " " );
}
#[ cfg( feature = "ansi_unicode" ) ]
mod unicode_tests
{
use crate::ansi::visual::visual_len_unicode;
#[ test ]
fn grapheme_emoji_with_modifier()
{
assert_eq!( visual_len_unicode( "ππ½" ), 1 );
}
#[ test ]
fn grapheme_flag_emoji()
{
assert_eq!( visual_len_unicode( "πΊπΈ" ), 1 );
}
#[ test ]
fn grapheme_combining_marks()
{
assert_eq!( visual_len_unicode( "e\u{0301}" ), 1 );
}
#[ test ]
fn grapheme_cjk()
{
assert_eq!( visual_len_unicode( "ζ₯ζ¬θͺ" ), 3 );
}
#[ test ]
fn grapheme_with_ansi()
{
assert_eq!( visual_len_unicode( "\x1b[33mζ₯ζ¬θͺ\x1b[0m" ), 3 );
assert_eq!( visual_len_unicode( "\x1b[31mππ½\x1b[0m" ), 1 );
}
}
}