use crate::{FromTextError, TextError, TextReader, TextWriter};
use basic_text_internals::is_basic_text_substr;
use basic_text_internals::unicode::{BOM, WJ};
use layered_io::Bufferable;
use std::borrow::{Borrow, BorrowMut, Cow};
use std::cmp::Ordering;
#[cfg(try_reserve)]
use std::collections::TryReserveError;
use std::ffi::OsStr;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash;
use std::io::{self, Read, Write};
use std::net::{SocketAddr, ToSocketAddrs};
use std::ops::{Deref, DerefMut, Index, Range, RangeFrom, RangeTo};
use std::path::Path;
#[cfg(pattern)]
use std::str::pattern::{Pattern, ReverseSearcher};
use std::str::{
self, Bytes, CharIndices, Chars, EncodeUtf16, EscapeDebug, EscapeDefault, EscapeUnicode,
FromStr, Lines, MatchIndices, Matches, RMatchIndices, RMatches,
};
use std::vec;
use utf8_io::WriteStr;
#[derive(PartialEq, Eq, Hash, Debug, Default)]
#[repr(transparent)]
pub struct TextSubstring(pub(crate) String);
#[derive(PartialEq, Eq, Hash, Debug)]
#[repr(transparent)]
pub struct TextSubstr(pub(crate) str);
impl TextSubstring {
#[inline]
#[must_use]
pub const fn new() -> Self {
Self(String::new())
}
#[inline]
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self(String::with_capacity(capacity))
}
#[inline]
pub fn from_text_vec(vec: Vec<u8>) -> Result<Self, FromTextError> {
Self::from_text(String::from_utf8(vec)?)
}
#[inline]
pub fn from_text(s: String) -> Result<Self, FromTextError> {
let bytes: Vec<u8> = Vec::new();
let mut writer = TextWriter::new(bytes);
match writer.write_str(&s).and_then(|()| writer.flush()) {
Ok(()) => (),
Err(_err) => {
writer.abandon();
let valid_up_to = compute_valid_up_to(&s);
return Err(FromTextError {
bytes: s.into_bytes(),
error: TextError { valid_up_to },
});
}
}
Ok(unsafe {
Self::from_text_vec_unchecked(
writer
.abandon_into_inner()
.abandon_into_inner()
.abandon_into_inner()
.unwrap(),
)
})
}
#[inline]
#[must_use]
pub fn from_text_bytes_lossy(v: &[u8]) -> Cow<TextSubstr> {
Cow::Owned(Self::from_text_lossy(&String::from_utf8_lossy(v)).into_owned())
}
#[inline]
#[must_use]
pub fn from_text_lossy(mut v: &str) -> Cow<TextSubstr> {
let mut reader = TextReader::new(v.as_bytes());
let mut text = String::new();
reader.read_to_string(&mut text).unwrap();
if let Some(suffix) = v.strip_prefix(BOM) {
text.insert(0, WJ);
v = suffix;
}
if !v.is_empty() && !v.ends_with(|c| matches!(c, '\n' | '\r')) {
let c = text.pop();
assert_eq!(c.unwrap(), '\n');
}
Cow::Owned(unsafe { Self::from_text_unchecked(text) })
}
#[inline]
#[must_use]
pub unsafe fn from_text_vec_unchecked(vec: Vec<u8>) -> Self {
Self::from_text_unchecked(String::from_utf8_unchecked(vec))
}
#[inline]
#[must_use]
pub const unsafe fn from_text_unchecked(s: String) -> Self {
Self(s)
}
#[inline]
#[must_use]
pub fn into_string(self) -> String {
self.0
}
#[inline]
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.0.into_bytes()
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
#[must_use]
pub fn as_text(&self) -> &TextSubstr {
self
}
#[inline]
#[must_use]
pub fn as_mut_text(&mut self) -> &mut TextSubstr {
self
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional);
}
#[inline]
pub fn reserve_exact(&mut self, additional: usize) {
self.0.reserve_exact(additional);
}
#[cfg(try_reserve)]
#[inline]
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.0.try_reserve(additional)
}
#[cfg(try_reserve)]
#[inline]
pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.0.try_reserve_exact(additional)
}
#[inline]
pub fn shrink_to_fit(&mut self) {
self.0.shrink_to_fit();
}
#[cfg(shrink_to)]
#[inline]
pub fn shrink_to(&mut self, min_capacity: usize) {
self.0.shrink_to(min_capacity);
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline]
pub unsafe fn as_mut_vec(&mut self) -> &mut Vec<u8> {
self.0.as_mut_vec()
}
#[inline]
pub unsafe fn as_mut_string(&mut self) -> &mut String {
&mut self.0
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
}
#[inline]
pub fn into_boxed_str(self) -> Box<str> {
self.0.into_boxed_str()
}
#[inline]
pub fn into_boxed_text(self) -> Box<TextSubstr> {
let slice = self.into_boxed_str();
unsafe { TextSubstr::from_boxed_text_unchecked(slice) }
}
}
#[cold]
fn compute_valid_up_to(s: &str) -> usize {
let mut begin = 0;
let mut end = s.len();
while begin != end {
let mut mid = begin + (end - begin) / 2;
while !s.is_char_boundary(mid) {
mid -= 1;
}
if mid == begin {
mid = begin + (end - begin) / 2 + 1;
while !s.is_char_boundary(mid) {
mid += 1;
}
if mid == end {
break;
}
}
let substr = &s[..mid];
if is_basic_text_substr(substr) {
begin = mid;
} else {
end = mid;
}
}
begin
}
impl AsRef<[u8]> for TextSubstring {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<OsStr> for TextSubstring {
#[inline]
fn as_ref(&self) -> &OsStr {
let s: &str = self.as_ref();
s.as_ref()
}
}
impl AsRef<Path> for TextSubstring {
#[inline]
fn as_ref(&self) -> &Path {
let s: &str = self.as_ref();
s.as_ref()
}
}
impl AsRef<str> for TextSubstring {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<TextSubstr> for TextSubstring {
#[inline]
fn as_ref(&self) -> &TextSubstr {
self
}
}
impl Clone for TextSubstring {
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl Deref for TextSubstring {
type Target = TextSubstr;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { TextSubstr::from_text_unchecked(&self.0) }
}
}
impl DerefMut for TextSubstring {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { TextSubstr::from_text_unchecked_mut(&mut self.0) }
}
}
impl Borrow<TextSubstr> for TextSubstring {
#[inline]
fn borrow(&self) -> &TextSubstr {
self
}
}
impl BorrowMut<TextSubstr> for TextSubstring {
#[inline]
fn borrow_mut(&mut self) -> &mut TextSubstr {
self
}
}
impl AsMut<TextSubstr> for TextSubstring {
#[inline]
fn as_mut(&mut self) -> &mut TextSubstr {
self
}
}
impl TextSubstr {
#[inline]
pub fn from_text_bytes(b: &[u8]) -> Result<&Self, TextError> {
Self::from_text(str::from_utf8(b)?)
}
#[inline]
pub fn from_text(s: &str) -> Result<&Self, TextError> {
if !is_basic_text_substr(s) {
let valid_up_to = compute_valid_up_to(s);
return Err(TextError { valid_up_to });
}
Ok(unsafe { Self::from_text_unchecked(s) })
}
#[inline]
pub fn from_text_bytes_mut(b: &mut [u8]) -> Result<&mut Self, TextError> {
Self::from_text_mut(str::from_utf8_mut(b)?)
}
#[inline]
pub fn from_text_mut(s: &mut str) -> Result<&mut Self, TextError> {
if !is_basic_text_substr(s) {
let valid_up_to = compute_valid_up_to(s);
return Err(TextError { valid_up_to });
}
Ok(unsafe { Self::from_text_unchecked_mut(s) })
}
#[inline]
pub unsafe fn from_text_bytes_unchecked(b: &[u8]) -> &Self {
Self::from_text_unchecked(str::from_utf8_unchecked(b))
}
#[inline]
pub unsafe fn from_text_unchecked(s: &str) -> &Self {
let ptr: *const str = s;
&*(ptr as *const Self)
}
#[inline]
pub unsafe fn from_text_bytes_unchecked_mut(b: &mut [u8]) -> &mut Self {
Self::from_text_unchecked_mut(str::from_utf8_unchecked_mut(b))
}
#[inline]
pub unsafe fn from_text_unchecked_mut(s: &mut str) -> &mut Self {
let ptr: *mut str = s;
&mut *(ptr as *mut Self)
}
#[inline]
pub unsafe fn from_boxed_text_bytes_unchecked(v: Box<[u8]>) -> Box<Self> {
let ptr = Box::into_raw(v);
Box::from_raw(ptr as *mut Self)
}
#[inline]
pub unsafe fn from_boxed_text_unchecked(v: Box<str>) -> Box<Self> {
let ptr = Box::into_raw(v);
Box::from_raw(ptr as *mut Self)
}
#[inline]
pub const fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
pub const fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline]
pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
self.0.as_bytes_mut()
}
#[inline]
pub const fn as_ptr(&self) -> *const u8 {
self.0.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.0.as_mut_ptr()
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn split_at(&self, mid: usize) -> (&Self, &Self) {
let (prefix, rest) = self.0.split_at(mid);
(
Self::from_text(prefix).unwrap(),
Self::from_text(rest).unwrap(),
)
}
#[inline]
pub fn split_at_mut(&mut self, mid: usize) -> (&mut Self, &mut Self) {
let (prefix, rest) = self.0.split_at_mut(mid);
(
Self::from_text_mut(prefix).unwrap(),
Self::from_text_mut(rest).unwrap(),
)
}
#[inline]
pub fn chars(&self) -> Chars {
self.0.chars()
}
#[inline]
pub fn char_indices(&self) -> CharIndices {
self.0.char_indices()
}
#[inline]
pub fn bytes(&self) -> Bytes {
self.0.bytes()
}
#[inline]
pub fn lines(&self) -> Lines {
self.0.lines()
}
#[inline]
pub fn encode_utf16(&self) -> EncodeUtf16<'_> {
self.0.encode_utf16()
}
#[cfg(pattern)]
#[inline]
pub fn contains<'a, P>(&'a self, pat: P) -> bool
where
P: Pattern<'a>,
{
self.0.contains(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn contains<'a>(&'a self, pat: &str) -> bool {
self.0.contains(pat)
}
#[cfg(pattern)]
#[inline]
pub fn starts_with<'a, P>(&'a self, pat: P) -> bool
where
P: Pattern<'a>,
{
self.0.starts_with(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn starts_with<'a, P>(&'a self, pat: &str) -> bool {
self.0.starts_with(pat)
}
#[cfg(pattern)]
#[inline]
pub fn ends_with<'a, P>(&'a self, pat: P) -> bool
where
P: Pattern<'a>,
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
{
self.0.ends_with(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn ends_with<'a>(&'a self, pat: &str) -> bool {
self.0.ends_with(pat)
}
#[cfg(pattern)]
#[inline]
pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>
where
P: Pattern<'a>,
{
self.0.find(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn find<'a>(&'a self, pat: &str) -> Option<usize> {
self.0.find(pat)
}
#[cfg(pattern)]
#[inline]
pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>
where
P: Pattern<'a>,
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
{
self.0.rfind(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn rfind<'a>(&'a self, pat: &str) -> Option<usize> {
self.0.rfind(pat)
}
#[cfg(pattern)]
#[inline]
pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>
where
P: Pattern<'a>,
{
self.0.matches(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn matches<'a>(&'a self, pat: &'a str) -> Matches<'a, &str> {
self.0.matches(pat)
}
#[cfg(pattern)]
#[inline]
pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>
where
P: Pattern<'a>,
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
{
self.0.rmatches(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn rmatches<'a>(&'a self, pat: &'a str) -> RMatches<'a, &str> {
self.0.rmatches(pat)
}
#[cfg(pattern)]
#[inline]
pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>
where
P: Pattern<'a>,
{
self.0.match_indices(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn match_indices<'a>(&'a self, pat: &'a str) -> MatchIndices<'a, &str> {
self.0.match_indices(pat)
}
#[cfg(pattern)]
#[inline]
pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>
where
P: Pattern<'a>,
<P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,
{
self.0.rmatch_indices(pat)
}
#[cfg(not(pattern))]
#[inline]
pub fn rmatch_indices<'a>(&'a self, pat: &'a str) -> RMatchIndices<'a, &str> {
self.0.rmatch_indices(pat)
}
#[inline]
pub fn trim(&self) -> &Self {
unsafe { Self::from_text_unchecked(self.0.trim()) }
}
#[inline]
pub fn trim_start(&self) -> &Self {
unsafe { Self::from_text_unchecked(self.0.trim_start()) }
}
#[inline]
pub fn trim_end(&self) -> &Self {
unsafe { Self::from_text_unchecked(self.0.trim_end()) }
}
#[inline]
pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
where
F: FromStr,
{
self.0.parse()
}
#[inline]
pub fn is_ascii(&self) -> bool {
self.0.is_ascii()
}
#[inline]
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
self.0.eq_ignore_ascii_case(&other.0)
}
#[inline]
pub fn into_boxed_bytes(self: Box<Self>) -> Box<[u8]> {
self.into_boxed_text().into_boxed_bytes()
}
#[inline]
pub fn into_boxed_text(self: Box<Self>) -> Box<str> {
self.into()
}
#[inline]
pub fn into_string(self: Box<Self>) -> String {
let slice = Box::<[u8]>::from(self);
unsafe { String::from_utf8_unchecked(slice.into_vec()) }
}
#[inline]
pub fn into_text_string(self: Box<Self>) -> TextSubstring {
unsafe { TextSubstring::from_text_unchecked(Self::into_string(self)) }
}
pub fn repeat(&self, n: usize) -> TextSubstring {
unsafe { TextSubstring::from_text_vec_unchecked(self.as_bytes().repeat(n)) }
}
#[inline]
pub fn escape_debug(&self) -> EscapeDebug<'_> {
self.0.escape_debug()
}
#[inline]
pub fn escape_default(&self) -> EscapeDefault<'_> {
self.0.escape_default()
}
#[inline]
pub fn escape_unicode(&self) -> EscapeUnicode {
self.0.escape_unicode()
}
}
impl AsRef<[u8]> for TextSubstr {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsRef<OsStr> for TextSubstr {
#[inline]
fn as_ref(&self) -> &OsStr {
self.0.as_ref()
}
}
impl AsRef<Path> for TextSubstr {
#[inline]
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
impl AsRef<str> for TextSubstr {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl AsRef<Self> for TextSubstr {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl Default for &TextSubstr {
#[inline]
fn default() -> Self {
unsafe { TextSubstr::from_text_unchecked("") }
}
}
impl Default for &mut TextSubstr {
#[inline]
fn default() -> Self {
unsafe { TextSubstr::from_text_bytes_unchecked_mut(&mut []) }
}
}
impl Display for TextSubstr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Ord for TextSubstr {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl PartialOrd<Self> for TextSubstr {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl PartialOrd<Self> for TextSubstring {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl ToOwned for TextSubstr {
type Owned = TextSubstring;
#[inline]
fn to_owned(&self) -> Self::Owned {
TextSubstring(self.0.to_owned())
}
}
impl ToSocketAddrs for TextSubstr {
type Iter = vec::IntoIter<SocketAddr>;
#[inline]
fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
self.0.to_socket_addrs()
}
}
impl From<Box<TextSubstr>> for Box<[u8]> {
#[inline]
fn from(s: Box<TextSubstr>) -> Self {
let ptr = Box::into_raw(s);
unsafe { Self::from_raw(ptr as *mut [u8]) }
}
}
impl From<Box<TextSubstr>> for Box<str> {
#[inline]
fn from(s: Box<TextSubstr>) -> Self {
let ptr = Box::into_raw(s);
unsafe { Self::from_raw(ptr as *mut str) }
}
}
impl From<Box<TextSubstr>> for TextSubstring {
#[inline]
fn from(s: Box<TextSubstr>) -> Self {
s.into_text_string()
}
}
impl From<&'_ Self> for TextSubstring {
#[inline]
fn from(s: &Self) -> Self {
s.clone()
}
}
impl From<&'_ mut TextSubstr> for TextSubstring {
#[inline]
fn from(s: &mut TextSubstr) -> Self {
s.to_owned()
}
}
impl From<&'_ TextSubstr> for TextSubstring {
#[inline]
fn from(s: &TextSubstr) -> Self {
s.to_owned()
}
}
impl From<Cow<'_, TextSubstr>> for Box<TextSubstr> {
#[inline]
fn from(cow: Cow<'_, TextSubstr>) -> Self {
match cow {
Cow::Borrowed(s) => Self::from(s),
Cow::Owned(s) => Self::from(s),
}
}
}
impl From<TextSubstring> for Box<TextSubstr> {
#[inline]
fn from(s: TextSubstring) -> Self {
s.into_boxed_text()
}
}
impl Clone for Box<TextSubstr> {
#[inline]
fn clone(&self) -> Self {
let buf: Box<[u8]> = self.as_bytes().into();
unsafe { TextSubstr::from_boxed_text_bytes_unchecked(buf) }
}
}
impl Default for Box<TextSubstr> {
#[inline]
fn default() -> Self {
unsafe { TextSubstr::from_boxed_text_bytes_unchecked(Box::default()) }
}
}
impl From<&TextSubstr> for Box<TextSubstr> {
#[inline]
fn from(s: &TextSubstr) -> Self {
unsafe { TextSubstr::from_boxed_text_bytes_unchecked(Box::from(s.as_bytes())) }
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for &'a TextSubstr {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let size = u.arbitrary_len::<u8>()?;
match TextSubstr::from_text_bytes(&u.peek_bytes(size).unwrap()) {
Ok(s) => {
u.bytes(size).unwrap();
Ok(s)
}
Err(e) => {
let i = e.valid_up_to();
let valid = u.bytes(i).unwrap();
let s = unsafe {
debug_assert!(TextSubstr::from_text_bytes(valid).is_ok());
TextSubstr::from_text_bytes_unchecked(valid)
};
Ok(s)
}
}
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let bytes = u.take_rest();
TextSubstr::from_text_bytes(bytes)
.map_err(|_| arbitrary::Error::IncorrectFormat)
.map(Into::into)
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and(<usize as arbitrary::Arbitrary>::size_hint(depth), (0, None))
}
}
#[cfg(feature = "arbitrary")]
impl<'a> arbitrary::Arbitrary<'a> for TextSubstring {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
<&TextSubstr as arbitrary::Arbitrary>::arbitrary(u).map(Into::into)
}
fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
<&TextSubstr as arbitrary::Arbitrary>::arbitrary_take_rest(u).map(Into::into)
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<&TextSubstr as arbitrary::Arbitrary>::size_hint(depth)
}
}
impl Index<Range<usize>> for TextSubstring {
type Output = TextSubstr;
#[inline]
fn index(&self, index: Range<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
impl Index<Range<usize>> for TextSubstr {
type Output = TextSubstr;
#[inline]
fn index(&self, index: Range<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
impl Index<RangeTo<usize>> for TextSubstring {
type Output = TextSubstr;
#[inline]
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
impl Index<RangeTo<usize>> for TextSubstr {
type Output = TextSubstr;
#[inline]
fn index(&self, index: RangeTo<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
impl Index<RangeFrom<usize>> for TextSubstring {
type Output = TextSubstr;
#[inline]
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
impl Index<RangeFrom<usize>> for TextSubstr {
type Output = TextSubstr;
#[inline]
fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
unsafe { TextSubstr::from_text_unchecked(self.0.index(index)) }
}
}
#[test]
fn normalize_string() {
let ring = "\u{30a}";
let unnormal = "A\u{30a}";
let unnormal_nl = "A\u{30a}\n";
let composed = TextSubstr::from_text("\u{c5}").unwrap();
let composed_nl = TextSubstr::from_text("\u{c5}\n").unwrap();
assert_eq!(
TextSubstr::from_text(unnormal).unwrap_err().valid_up_to(),
1
);
TextSubstr::from_text(ring).unwrap();
assert_eq!(composed, &TextSubstring::from_text_lossy(unnormal));
assert_eq!(composed_nl, &TextSubstring::from_text_lossy(unnormal_nl));
}
#[test]
fn validate_string() {
assert!(TextSubstr::from_text_bytes(b"").is_ok());
assert_eq!(
TextSubstr::from_text_bytes(b"\xff")
.unwrap_err()
.valid_up_to(),
0
);
}
#[test]
fn split_escape() {
assert_eq!(
TextSubstr::from_text_bytes(b"\x1b[p")
.unwrap_err()
.valid_up_to(),
0
);
assert_eq!(
TextSubstr::from_text_bytes(b"\x1b[!")
.unwrap_err()
.valid_up_to(),
0
);
assert_eq!(
TextSubstr::from_text_bytes(b"\x1b[")
.unwrap_err()
.valid_up_to(),
0
);
assert_eq!(
TextSubstr::from_text_bytes(b"\x1b")
.unwrap_err()
.valid_up_to(),
0
);
}