#[inline(never)]
pub(crate) fn display_width(text: &str) -> usize {
let mut width = 0;
let mut control_sequence = false;
let control_terminate: char = 'm';
for ch in text.chars() {
if ch.is_ascii_control() {
control_sequence = true;
} else if control_sequence && ch == control_terminate {
control_sequence = false;
continue;
}
if !control_sequence {
width += ch_width(ch);
}
}
width
}
#[cfg(feature = "unicode")]
fn ch_width(ch: char) -> usize {
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
}
#[cfg(not(feature = "unicode"))]
fn ch_width(_: char) -> usize {
1
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "unicode")]
use unicode_width::UnicodeWidthChar;
#[test]
fn emojis_have_correct_width() {
use unic_emoji_char::is_emoji;
for ch in '\u{1}'..'\u{FF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode")]
assert_eq!(ch.width().unwrap(), 1, "char: {desc}");
#[cfg(not(feature = "unicode"))]
assert_eq!(ch_width(ch), 1, "char: {desc}");
}
}
for ch in '\u{FF}'..'\u{2FFFF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode")]
assert!(ch.width().unwrap() <= 2, "char: {desc}");
#[cfg(not(feature = "unicode"))]
assert_eq!(ch_width(ch), 1, "char: {desc}");
}
}
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_works() {
assert_eq!("Café Plain".len(), 11); assert_eq!(display_width("Café Plain"), 10);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_narrow_emojis() {
assert_eq!(display_width("⁉"), 1);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_narrow_emojis_variant_selector() {
assert_eq!(display_width("⁉\u{fe0f}"), 1);
}
#[test]
#[cfg(feature = "unicode")]
fn display_width_emojis() {
assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
}
}