#[cfg(test)]
mod tests;
use unicode_segmentation::{GraphemeIndices, UnicodeSegmentation};
#[derive(Clone)]
pub struct PrintPositions<'a> {
string: &'a str,
cur_offset: usize,
next_offset: usize,
gi_iterator: GraphemeIndices<'a>,
}
#[inline]
pub fn print_positions<'a>(s: &'a str) -> PrintPositions<'a> {
let iter = UnicodeSegmentation::grapheme_indices(s, true);
PrintPositions {
string: s,
cur_offset: 0,
next_offset: 0,
gi_iterator: iter,
}
}
impl<'a> PrintPositions<'a> {
#[inline]
pub fn as_str(&self) -> &'a str {
&self.string[self.cur_offset..self.string.len()]
}
}
impl<'a> Iterator for PrintPositions<'a> {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.next_offset > self.string.len() {
return None;
};
enum EscapeState {
Normal,
EscapeSeen, CSISeen, OSCSeen, OSCSeen1, }
let mut escape_state = EscapeState::Normal;
while self.next_offset < self.string.len() {
let grap = self.gi_iterator.next().expect("already checked not at EOS");
debug_assert_eq!(
grap.0, self.next_offset,
"offset of retrieved grap (left) not at start of rest of string (right)",
);
self.next_offset += grap.1.len();
let ascii_byte = grap.1.as_bytes()[0];
match escape_state {
EscapeState::Normal => {
if ascii_byte == 0x1b {
escape_state = EscapeState::EscapeSeen;
} else {
break; }
}
EscapeState::EscapeSeen => match ascii_byte {
b'[' => {
escape_state = EscapeState::CSISeen;
}
b']' => {
escape_state = EscapeState::OSCSeen;
}
0x40..=0x5F => {
escape_state = EscapeState::Normal;
}
_ => {
debug_assert!(
true, "unexpected char {ascii_byte} following ESC, terminating escape"
);
escape_state = EscapeState::Normal;
}
},
EscapeState::CSISeen => {
if (0x40..=0x7e).contains(&ascii_byte) {
escape_state = EscapeState::Normal;
} else if (0x20..=0x3f).contains(&ascii_byte) { } else {
debug_assert!(
true, "unexpected char {ascii_byte} in CSI sequence, terminating escape"
);
escape_state = EscapeState::Normal;
}
}
EscapeState::OSCSeen => {
if ascii_byte == 0x07 {
escape_state = EscapeState::Normal;
} else if ascii_byte == 0x1b {
escape_state = EscapeState::OSCSeen1;
} }
EscapeState::OSCSeen1 => {
match ascii_byte {
0x5c => {
escape_state = EscapeState::Normal;
}
0x1b => {
escape_state = EscapeState::OSCSeen1;
}
_ => {
escape_state = EscapeState::OSCSeen;
}
}
}
}
}
while self.next_offset < self.string.len()
&& self.string.as_bytes()[self.next_offset] == 0x1b
{
if self.next_offset + 2 <= self.string.len()
&& self.string[self.next_offset..].starts_with("\x1bc")
{
self.gi_iterator.next();
let last = self.gi_iterator.next().expect("must be >=2");
self.next_offset += 1 + last.1.len();
} else if self.next_offset + 3 <= self.string.len()
&& self.string[self.next_offset..].starts_with("\x1b[m")
{
self.gi_iterator.next();
self.gi_iterator.next();
let last = self.gi_iterator.next().expect("must be >=3");
self.next_offset += 2 + last.1.len();
} else if self.next_offset + 4 <= self.string.len()
&& self.string[self.next_offset..].starts_with("\x1b[0m")
{
self.gi_iterator.next();
self.gi_iterator.next();
self.gi_iterator.next();
let last = self.gi_iterator.next().expect("must be >=4");
self.next_offset += 3 + last.1.len();
} else {
break; }
}
if self.next_offset <= self.cur_offset {
return None;
} else {
let retval = (self.cur_offset, self.next_offset);
self.cur_offset = self.next_offset;
return Some(retval);
}
}
}
pub struct PrintPositionData<'a>(PrintPositions<'a>);
#[inline]
pub fn print_position_data<'a>(s: &'a str) -> PrintPositionData<'a> {
PrintPositionData(print_positions(s))
}
impl<'a> PrintPositionData<'a> {
#[inline]
pub fn as_str(&self) -> &'a str {
&self.0.string[self.0.cur_offset..self.0.string.len()]
}
}
impl<'a> Iterator for PrintPositionData<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if let Some((start, end)) = self.0.next() {
Some(&self.0.string[start..end])
} else {
None
}
}
}