use crate::buf::opt::BufferOptions;
use ascii::AsciiChar;
use compact_str::CompactString;
use icu::properties::CodePointMapData;
use icu::properties::CodePointSetData;
use icu::properties::props::EastAsianWidth;
use icu::properties::props::WhiteSpace;
use std::fmt;
pub struct AsciiControlCodeFormatter {
value: AsciiChar,
}
impl From<AsciiChar> for AsciiControlCodeFormatter {
fn from(value: AsciiChar) -> Self {
debug_assert!(value.is_ascii_control());
AsciiControlCodeFormatter { value }
}
}
impl fmt::Display for AsciiControlCodeFormatter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self.value {
AsciiChar::Null => write!(f, "^@"),
AsciiChar::SOH => write!(f, "^A"),
AsciiChar::SOX => write!(f, "^B"),
AsciiChar::ETX => write!(f, "^C"),
AsciiChar::EOT => write!(f, "^D"),
AsciiChar::ENQ => write!(f, "^E"),
AsciiChar::ACK => write!(f, "^F"),
AsciiChar::Bell => write!(f, "^G"),
AsciiChar::BackSpace => write!(f, "^H"),
AsciiChar::Tab => write!(f, "\t"), AsciiChar::LineFeed => writeln!(f, "^J"), AsciiChar::VT => write!(f, "^K"),
AsciiChar::FF => write!(f, "^L"),
AsciiChar::CarriageReturn => write!(f, "^M"), AsciiChar::SI => write!(f, "^N"),
AsciiChar::SO => write!(f, "^0"),
AsciiChar::DLE => write!(f, "^P"),
AsciiChar::DC1 => write!(f, "^Q"),
AsciiChar::DC2 => write!(f, "^R"),
AsciiChar::DC3 => write!(f, "^S"),
AsciiChar::DC4 => write!(f, "^T"),
AsciiChar::NAK => write!(f, "^U"),
AsciiChar::SYN => write!(f, "^V"),
AsciiChar::ETB => write!(f, "^W"),
AsciiChar::CAN => write!(f, "^X"),
AsciiChar::EM => write!(f, "^Y"),
AsciiChar::SUB => write!(f, "^Z"),
AsciiChar::ESC => write!(f, "^["),
AsciiChar::FS => write!(f, "^\\"),
AsciiChar::GS => write!(f, "^]"),
AsciiChar::RS => write!(f, "^^"),
AsciiChar::US => write!(f, "^_"),
_ => unreachable!(),
}
}
}
pub fn char_width(opt: &BufferOptions, c: char) -> usize {
if c.is_ascii_control() {
let ac = AsciiChar::from_ascii(c).unwrap();
match ac {
AsciiChar::Tab => opt.tab_stop() as usize,
AsciiChar::LineFeed | AsciiChar::CarriageReturn => 0,
_ => {
let ascii_formatter = AsciiControlCodeFormatter::from(ac);
format!("{ascii_formatter}").len()
}
}
} else {
match CodePointMapData::<EastAsianWidth>::new().get(c) {
EastAsianWidth::Wide => 2_usize,
EastAsianWidth::Fullwidth => 2_usize,
EastAsianWidth::Halfwidth => 1_usize,
EastAsianWidth::Narrow => 1_usize,
EastAsianWidth::Ambiguous => 1_usize,
EastAsianWidth::Neutral => 1_usize,
_ => 1_usize,
}
}
}
pub fn char_symbol(opt: &BufferOptions, c: char) -> CompactString {
if c.is_ascii_control() {
let ac = AsciiChar::from_ascii(c).unwrap();
match ac {
AsciiChar::Tab => {
CompactString::from(" ".repeat(opt.tab_stop() as usize))
}
AsciiChar::LineFeed | AsciiChar::CarriageReturn => CompactString::new(""),
_ => {
let ascii_formatter = AsciiControlCodeFormatter::from(ac);
CompactString::from(format!("{ascii_formatter}"))
}
}
} else {
CompactString::from(c.to_string())
}
}
pub fn str_width(opt: &BufferOptions, s: &str) -> usize {
s.chars().map(|c| char_width(opt, c)).sum()
}
pub fn str_symbols(opt: &BufferOptions, s: &str) -> CompactString {
s.chars().map(|c| char_symbol(opt, c)).fold(
CompactString::with_capacity(s.len()),
|mut init_symbol, mut symbol| {
init_symbol.push_str(symbol.as_mut_str());
init_symbol
},
)
}
pub fn char_is_whitespace(c: char) -> bool {
CodePointSetData::new::<WhiteSpace>().contains(c)
}