use unicode_width::UnicodeWidthStr;
use crate::codes;
pub struct TermTextSpan<'a> {
text: &'a str,
columns: usize,
chars: usize,
control: bool,
}
impl<'a> TermTextSpan<'a> {
pub fn text(&self) -> &'a str {
self.text
}
pub fn chars(&self) -> usize {
self.chars
}
pub fn columns(&self) -> usize {
self.columns
}
pub fn is_control(&self) -> bool {
self.control
}
pub fn create(text: &str) -> (TermTextSpan<'_>, &str) {
let found = text
.char_indices()
.chain([(usize::MAX, '\0')])
.enumerate()
.find(|(_, (_, c))| c.is_ascii_control());
let Some((idx, (ind, chr))) = found else {
unreachable!();
};
if ind == usize::MAX && chr == '\0' {
return (
TermTextSpan {
text,
columns: text.width(),
chars: idx,
control: false,
},
"",
);
}
if ind != 0 {
return Self::split_from(text, ind, idx, false);
}
if chr != codes::ESC {
return Self::split_from(text, 1, 1, true);
}
let Some(chr) = text.chars().nth(1) else {
return Self::split_from(text, 1, 1, true);
};
match chr as u32 {
0x50 | 0x5d | 0x5e | 0x5f => Self::end_with_str(text, "\x1b\x5c"),
0x5b => Self::end_with_pat(text, 2, |c| {
(0x40..0x7f).contains(&(c as u32))
}),
0x4e | 0x4f => Self::split_from(text, 3, 3, true),
0x40..=0x5f => Self::split_from(text, 2, 2, true),
_ => Self::split_from(text, 1, 1, true),
}
}
fn split_from(
text: &str,
ind: usize,
chars: usize,
control: bool,
) -> (TermTextSpan<'_>, &str) {
let columns = if control { 0 } else { text[..ind].width() };
(
TermTextSpan {
text: &text[..ind],
columns,
chars,
control,
},
&text[ind..],
)
}
fn end_with_str<'b>(
text: &'b str,
pat: &str,
) -> (TermTextSpan<'b>, &'b str) {
if let Some(p) = text.find(pat) {
let ind = p + pat.len();
Self::split_from(text, ind, text[..ind].chars().count(), true)
} else {
(
TermTextSpan {
text,
columns: 0,
chars: text.chars().count(),
control: true,
},
"",
)
}
}
fn end_with_pat(
text: &str,
skip: usize,
f: impl Fn(char) -> bool,
) -> (TermTextSpan<'_>, &str) {
let end = text[skip..]
.char_indices()
.chain([(usize::MAX, '0')])
.enumerate()
.find(|(_, (_, c))| f(*c));
let Some((idx, (ind, c))) = end else {
unreachable!();
};
if ind == usize::MAX && c == '0' {
(
TermTextSpan {
text,
columns: 0,
chars: idx + skip,
control: true,
},
"",
)
} else {
Self::split_from(
text,
ind + c.len_utf8() + skip,
idx + 1 + skip,
true,
)
}
}
}