use unicode_width::UnicodeWidthChar;
pub(super) fn caret_width(c: char) -> bool {
let code = c as u32;
code <= 0x1f || (0x7f..=0x9f).contains(&code)
}
pub(super) fn caret_notation(c: char) -> String {
let code = c as u32;
match code {
0..=0x1f | 0x80..=0x9f => {
let letter = char::from_u32(code % 0x40 + 0x40).unwrap_or('?');
format!("^{letter}")
}
0x7f => "^?".to_string(),
_ => c.to_string(),
}
}
pub(super) enum Esc {
Sgr,
Osc8,
Visible,
}
pub(super) fn parse_escape(bytes: &[u8], start: usize) -> (usize, Esc) {
let mut i = start + 1;
if i >= bytes.len() {
return (i, Esc::Visible);
}
match bytes[i] {
b'[' => {
i += 1;
while i < bytes.len() && (0x20..=0x3f).contains(&bytes[i]) {
i += 1;
}
let is_sgr = i < bytes.len() && bytes[i] == b'm';
if i < bytes.len() && (0x40..=0x7e).contains(&bytes[i]) {
i += 1;
}
(i, if is_sgr { Esc::Sgr } else { Esc::Visible })
}
b']' => {
i += 1;
let osc8 = i + 1 < bytes.len() && bytes[i] == b'8' && bytes[i + 1] == b';';
let mut terminated = false;
while i < bytes.len() && bytes[i] != 0x07 && bytes[i] != 0x1b {
i += 1;
}
if i < bytes.len() && bytes[i] == 0x1b {
i += 1;
if i < bytes.len() && bytes[i] == b'\\' {
i += 1;
terminated = true;
}
} else if i < bytes.len() && bytes[i] == 0x07 {
i += 1;
terminated = true;
}
let kind = if osc8 && terminated {
Esc::Osc8
} else {
Esc::Visible
};
(i, kind)
}
b if (0x20..=0x7e).contains(&b) => (i + 1, Esc::Visible),
_ => (i, Esc::Visible),
}
}
pub(super) fn escape_display(seq: &str) -> String {
let mut out = String::new();
for c in seq.chars() {
if c == '\u{1b}' {
out.push_str("^[");
} else if caret_width(c) {
out.push_str(&caret_notation(c));
} else {
out.push(c);
}
}
out
}
pub(super) fn display_width(text: &str) -> usize {
text.chars().map(|c| c.width().unwrap_or(1)).sum()
}