use alloc::{borrow::Cow, str::from_utf8_unchecked, string::String, vec::Vec};
pub trait ExpandTabs<'a> {
fn expand_tabs(self, spaces: usize) -> Cow<'a, str>;
}
fn remove_tabs(s: &str) -> Cow<'_, str> {
let bytes = s.as_bytes();
let length = bytes.len();
let mut p = 0;
loop {
if p == length {
return Cow::Borrowed(s);
}
if bytes[p] == b'\t' {
break;
} else {
p += 1;
}
}
let heading_normal_characters_end_index = p;
p += 1;
loop {
if p == length {
return Cow::Borrowed(unsafe {
from_utf8_unchecked(&bytes[..heading_normal_characters_end_index])
});
}
if bytes[p] == b'\t' {
p += 1;
} else {
break;
}
}
let following_tab_characters_end_index = p;
p += 1;
loop {
if p == length {
if heading_normal_characters_end_index == 0 {
return Cow::Borrowed(unsafe {
from_utf8_unchecked(&bytes[following_tab_characters_end_index..])
});
} else {
let mut new_v = Vec::with_capacity(
heading_normal_characters_end_index + length
- following_tab_characters_end_index,
);
new_v.extend_from_slice(bytes[..heading_normal_characters_end_index].as_ref());
new_v.extend_from_slice(bytes[following_tab_characters_end_index..].as_ref());
return Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) });
}
}
if bytes[p] == b'\t' {
break;
} else {
p += 1;
}
}
let following_normal_characters_end_index = p;
if p < length {
p += 1;
loop {
if p == length {
return Cow::Borrowed(unsafe {
from_utf8_unchecked(
&bytes[following_tab_characters_end_index
..following_normal_characters_end_index],
)
});
}
if bytes[p] == b'\t' {
p += 1;
} else {
break;
}
}
}
let mut new_v =
bytes[following_tab_characters_end_index..following_normal_characters_end_index].to_vec();
let mut start = p;
p += 1;
loop {
if p == length {
break;
}
if bytes[p] == b'\t' {
new_v.extend_from_slice(&bytes[start..p]);
start = p + 1;
}
p += 1;
}
new_v.extend_from_slice(&bytes[start..p]);
Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) })
}
impl<'a> ExpandTabs<'a> for &'a str {
fn expand_tabs(self, spaces: usize) -> Cow<'a, str> {
if spaces == 0 {
return remove_tabs(self);
}
let s = self;
let bytes = s.as_bytes();
let length = bytes.len();
let mut p = 0;
loop {
if p == length {
return Cow::Borrowed(s);
}
if bytes[p] == b'\t' {
break;
}
p += 1;
}
let tab_count = bytes[p..].iter().filter(|&&b| b == b'\t').count();
let capacity = bytes.len() + (tab_count * (spaces - 1));
let mut new_v = Vec::with_capacity(capacity);
new_v.extend_from_slice(&bytes[..p]);
new_v.resize(new_v.len() + spaces, b' ');
p += 1;
let mut start = p;
loop {
if p == length {
break;
}
if bytes[p] == b'\t' {
new_v.extend_from_slice(&bytes[start..p]);
new_v.resize(new_v.len() + spaces, b' ');
p += 1;
start = p;
} else {
p += 1;
}
}
new_v.extend_from_slice(&bytes[start..p]);
Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) })
}
}
impl<'a> ExpandTabs<'a> for Cow<'a, str> {
#[inline]
fn expand_tabs(self, spaces: usize) -> Cow<'a, str> {
match self {
Cow::Borrowed(s) => s.expand_tabs(spaces),
Cow::Owned(mut s) if spaces == 0 => {
if s.as_bytes().contains(&b'\t') {
s.retain(|c| c != '\t');
}
Cow::Owned(s)
},
Cow::Owned(s) => Cow::Owned(crate::cow_into_owned!(s, s.as_str().expand_tabs(spaces),)),
}
}
}