#[inline]
pub fn glyph_cols(c: char) -> u16 {
#[cfg(feature = "unicode")]
{
unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16
}
#[cfg(not(feature = "unicode"))]
{
let _ = c;
1
}
}
pub fn str_cols(s: &str) -> u16 {
s.chars().map(glyph_cols).fold(0u16, u16::saturating_add)
}
pub fn truncate_to_cols(s: &str, max: u16) -> String {
let mut out = String::new();
let mut used = 0u16;
for c in s.chars() {
let w = glyph_cols(c);
if used + w > max {
break;
}
out.push(c);
used += w;
}
out
}
pub fn sanitize(s: &str) -> String {
s.chars()
.map(|c| if c.is_control() { ' ' } else { c })
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncate_respects_display_width() {
assert_eq!(truncate_to_cols("abc", 2), "ab");
assert_eq!(truncate_to_cols("abc", 0), "");
}
#[test]
fn sanitize_neutralizes_escape_sequences() {
assert_eq!(sanitize("done\x1b[2J\x1b[H"), "done [2J [H");
assert_eq!(sanitize("plain caption"), "plain caption");
let caption = "a\x1b[31mb";
assert_eq!(sanitize(caption).chars().count(), caption.chars().count());
assert!(!sanitize(caption).chars().any(char::is_control));
}
#[cfg(feature = "unicode")]
#[test]
fn wide_glyphs_count_two() {
assert_eq!(glyph_cols('a'), 1);
assert_eq!(glyph_cols('世'), 2);
assert_eq!(str_cols("a世"), 3);
assert_eq!(truncate_to_cols("a世", 3), "a世"); assert_eq!(truncate_to_cols("世界", 3), "世"); }
}