use gap_buf::GapBuffer;
use crate::{
buffer::Change,
text::{
strs::{Strs, line_ranges::LineRanges},
utils::implPartialEq,
},
};
#[derive(Default, Clone, bincode::Decode, bincode::Encode)]
pub struct StrsBuf {
pub(super) gapbuf: GapBuffer<u8>,
pub(super) line_ranges: LineRanges,
version: u64,
}
impl StrsBuf {
#[doc(hidden)]
#[track_caller]
pub(crate) fn new(string: String) -> Self {
assert!(
string.len() <= u32::MAX as usize,
"For now, you can't have a Text larger than u32::MAX"
);
let buf = GapBuffer::from(string.into_bytes());
let slices = unsafe {
let (s0, s1) = buf.as_slices();
[str::from_utf8_unchecked(s0), str::from_utf8_unchecked(s1)]
};
let records = LineRanges::new(slices);
let mut buf = Self {
gapbuf: buf,
line_ranges: records,
version: 0,
};
if buf.bytes().next_back().is_none_or(|b| b != b'\n') {
let end = buf.end_point();
buf.apply_change(Change::str_insert("\n", end));
}
buf
}
#[track_caller]
pub(crate) fn apply_change(&mut self, change: Change<&str>) {
assert!(
self.len() + change.added_str().len() - change.taken_str().len() <= u32::MAX as usize,
"For now, you can't have a Text larger than u32::MAX"
);
assert_utf8_boundary(self, change.start().byte());
assert_utf8_boundary(self, change.taken_end().byte());
let edit = change.added_str();
let start = change.start();
let range = start.byte()..change.taken_end().byte();
self.gapbuf.splice(range, edit.bytes());
let start_rec = [start.byte(), start.char(), start.line()];
let old_len = [
change.taken_end().byte() - start.byte(),
change.taken_end().char() - start.char(),
change.taken_end().line() - start.line(),
];
let new_len = [
change.added_end().byte() - start.byte(),
change.added_end().char() - start.char(),
change.added_end().line() - start.line(),
];
let array = unsafe {
let (s0, s1) = self.gapbuf.as_slices();
[str::from_utf8_unchecked(s0), str::from_utf8_unchecked(s1)]
};
self.line_ranges
.transform(start_rec, old_len, new_len, array);
}
pub fn increment_version(&mut self) {
self.version += 1;
}
pub fn version(&self) -> u64 {
self.version
}
}
impl std::ops::Deref for StrsBuf {
type Target = Strs;
fn deref(&self) -> &Self::Target {
Strs::new(self, 0, self.gapbuf.len() as u32)
}
}
#[must_use]
#[inline]
pub const fn utf8_char_width(b: u8) -> usize {
const UTF8_CHAR_WIDTH: &[u8; 256] = &[
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
UTF8_CHAR_WIDTH[b as usize] as usize
}
impl Eq for StrsBuf {}
implPartialEq!(bytes: StrsBuf, other: StrsBuf, {
let (l_s0, l_s1) = bytes.gapbuf.as_slices();
let (r_s0, r_s1) = other.gapbuf.as_slices();
(l_s0.len() + l_s1.len() == r_s0.len() + r_s1.len()) && l_s0.iter().chain(l_s1).eq(r_s0.iter().chain(r_s1))
});
implPartialEq!(bytes: StrsBuf, other: &str, {
let [s0, s1] = bytes.to_array();
other.len() == s0.len() + s1.len() && &other[..s0.len()] == s0 && &other[s0.len()..] == s1
});
implPartialEq!(bytes: StrsBuf, other: String, bytes == &&other.as_str());
implPartialEq!(str: &str, other: StrsBuf, other == *str);
implPartialEq!(string: String, other: StrsBuf, other == *string);
impl Eq for &Strs {}
implPartialEq!(strs: &Strs, other: &Strs, {
let [l_s0, l_s1] = strs.to_array();
let [r_s0, r_s1] = other.to_array();
(l_s0.len() + l_s1.len() == r_s0.len() + r_s1.len()) && l_s0.bytes().chain(l_s1.bytes()).eq(r_s0.bytes().chain(r_s1.bytes()))
});
implPartialEq!(strs: &Strs, other: &str, {
let [s0, s1] = strs.to_array();
other.len() == s0.len() + s1.len() && &other[..s0.len()] == s0 && &other[s0.len()..] == s1
});
implPartialEq!(strs: &Strs, other: String, strs == &&other.as_str());
implPartialEq!(str: &str, other: &Strs, other == *str);
implPartialEq!(string: String, other: &Strs, other == *string);
macro_rules! implFromToString {
($T:ty) => {
impl From<$T> for StrsBuf {
fn from(value: $T) -> Self {
StrsBuf::new(<$T as ToString>::to_string(&value))
}
}
};
}
implFromToString!(u8);
implFromToString!(u16);
implFromToString!(u32);
implFromToString!(u64);
implFromToString!(u128);
implFromToString!(usize);
implFromToString!(i8);
implFromToString!(i16);
implFromToString!(i32);
implFromToString!(i64);
implFromToString!(i128);
implFromToString!(isize);
implFromToString!(f32);
implFromToString!(f64);
implFromToString!(char);
implFromToString!(&str);
implFromToString!(String);
implFromToString!(Box<str>);
implFromToString!(std::rc::Rc<str>);
implFromToString!(std::sync::Arc<str>);
implFromToString!(std::borrow::Cow<'_, str>);
implFromToString!(std::io::Error);
implFromToString!(Box<dyn std::error::Error>);
impl From<std::path::PathBuf> for StrsBuf {
fn from(value: std::path::PathBuf) -> Self {
let value = value.to_string_lossy();
Self::from(value)
}
}
impl From<&std::path::Path> for StrsBuf {
fn from(value: &std::path::Path) -> Self {
let value = value.to_string_lossy();
Self::from(value)
}
}
impl std::fmt::Debug for StrsBuf {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StrsBuf")
.field("buf", &self[..].to_array())
.field("records", &self.line_ranges)
.finish()
}
}
#[track_caller]
pub fn assert_utf8_boundary(bytes: &StrsBuf, idx: usize) {
assert!(
bytes
.gapbuf
.get(idx)
.is_none_or(|b| utf8_char_width(*b) != 0),
"byte index {} is not a valid char boundary; it is inside '{}'",
idx,
{
let (n, len) = bytes
.gapbuf
.range(..idx)
.iter()
.rev()
.enumerate()
.find_map(|(i, &b)| (utf8_char_width(b) != 0).then_some((i, utf8_char_width(b))))
.unwrap();
String::from_utf8(
bytes
.gapbuf
.range(idx - (n + 1)..idx - (n + 1) + len)
.iter()
.copied()
.collect(),
)
.unwrap()
}
);
}