use super::metrics::{ChunkSummary, SummaryUpTo, ToByteOffset};
use super::utils::{debug_no_quotes, panic_messages as panic};
use crate::tree::{Metric, Summarize};
#[derive(Copy, Clone, Default)]
pub struct GapSlice<'a> {
pub(super) bytes: &'a [u8],
pub(super) left_summary: ChunkSummary,
pub(super) len_right: u16,
}
impl core::fmt::Debug for GapSlice<'_> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str("\"")?;
debug_no_quotes(self.left_chunk(), f)?;
write!(f, "{:~^1$}", "", self.len_gap())?;
debug_no_quotes(self.right_chunk(), f)?;
f.write_str("\"")
}
}
impl PartialEq<GapSlice<'_>> for &str {
fn eq(&self, rhs: &GapSlice<'_>) -> bool {
self.len() == rhs.len()
&& rhs.left_chunk() == &self[..rhs.len_left()]
&& rhs.right_chunk() == &self[rhs.len_left()..]
}
}
impl<'a> GapSlice<'a> {
#[track_caller]
#[inline]
pub(super) fn assert_char_boundary(&self, byte_offset: usize) {
debug_assert!(byte_offset <= self.len());
if !self.is_char_boundary(byte_offset) {
if byte_offset < self.len_left() {
panic::byte_offset_not_char_boundary(
self.left_chunk(),
byte_offset,
)
} else {
panic::byte_offset_not_char_boundary(
self.right_chunk(),
byte_offset - self.len_left(),
)
}
}
}
pub(super) fn assert_invariants(&self) {
assert_eq!(self.left_summary, ChunkSummary::from(self.left_chunk()));
if self.len_right() == 0 {
assert_eq!(self.len_left(), self.bytes.len());
} else if self.len_left() == 0 {
assert_eq!(self.len_right(), self.bytes.len());
}
}
#[inline]
pub(super) fn byte(&self, byte_index: usize) -> u8 {
debug_assert!(byte_index < self.len());
if byte_index < self.len_left() {
self.left_chunk().as_bytes()[byte_index]
} else {
self.right_chunk().as_bytes()[byte_index - self.len_left()]
}
}
#[inline]
fn left_measure<M>(&self) -> M
where
M: Metric<ChunkSummary>,
{
M::measure(&self.left_summary)
}
#[inline]
pub(super) fn truncate_last_char(
&mut self,
summary: ChunkSummary,
) -> ChunkSummary {
debug_assert!(self.len() > 0);
debug_assert_eq!(summary, self.summarize());
use core::cmp::Ordering;
let last_char = self
.last_chunk()
.chars()
.next_back()
.expect("this slice isn't empty");
let removed_summary = ChunkSummary::from(last_char);
let len_utf8 = removed_summary.bytes();
match self.len_right.cmp(&(len_utf8 as u16)) {
Ordering::Less => {
self.left_summary -= removed_summary;
self.bytes = &self.bytes[..self.len_left()];
self.left_summary
},
Ordering::Greater => {
self.len_right -= len_utf8 as u16;
self.bytes = &self.bytes[..self.bytes.len() - len_utf8];
summary - removed_summary
},
Ordering::Equal => {
self.len_right = 0;
self.bytes = &self.bytes[..self.len_left()];
self.left_summary
},
}
}
#[inline]
pub(super) fn truncate_trailing_line_break(
&mut self,
summary: ChunkSummary,
) -> ChunkSummary {
debug_assert_eq!(summary, self.summarize());
if !self.has_trailing_newline() {
return summary;
}
let mut new_summary = self.truncate_last_char(summary);
if self.last_chunk().ends_with('\r') {
new_summary = self.truncate_last_char(new_summary)
}
new_summary
}
#[inline]
pub(super) fn empty() -> Self {
Self::default()
}
#[inline]
pub(super) fn has_trailing_newline(&self) -> bool {
self.last_chunk().ends_with('\n')
}
#[inline]
pub(super) fn is_char_boundary(&self, byte_offset: usize) -> bool {
debug_assert!(byte_offset <= self.len());
if byte_offset <= self.len_left() {
self.left_chunk().is_char_boundary(byte_offset)
} else {
self.right_chunk().is_char_boundary(byte_offset - self.len_left())
}
}
#[inline]
pub(super) fn last_chunk(&self) -> &'a str {
if self.len_right() == 0 {
self.left_chunk()
} else {
self.right_chunk()
}
}
#[inline]
pub(super) fn left_chunk(&self) -> &'a str {
unsafe {
core::str::from_utf8_unchecked(&self.bytes[..self.len_left()])
}
}
#[inline]
pub(super) fn len(&self) -> usize {
self.len_left() + self.len_right()
}
#[inline]
pub(super) fn len_gap(&self) -> usize {
self.bytes.len() - self.len()
}
#[inline]
pub(super) fn len_left(&self) -> usize {
self.left_summary.bytes()
}
#[inline]
pub(super) fn len_right(&self) -> usize {
self.len_right as _
}
#[inline]
pub(super) fn right_chunk(&self) -> &'a str {
unsafe {
core::str::from_utf8_unchecked(
&self.bytes[self.bytes.len() - self.len_right()..],
)
}
}
#[inline]
fn right_summary(&self, summary: ChunkSummary) -> ChunkSummary {
debug_assert_eq!(summary, self.summarize());
summary - self.left_summary
}
#[track_caller]
#[inline]
pub fn split_at_offset<M>(
&self,
mut offset: M,
summary: ChunkSummary,
) -> ((Self, ChunkSummary), (Self, ChunkSummary))
where
M: Metric<ChunkSummary> + ToByteOffset + SummaryUpTo,
{
debug_assert_eq!(summary, self.summarize());
debug_assert!(offset <= M::measure(&summary));
if offset <= self.left_measure::<M>() {
let byte_offset: usize = offset.to_byte_offset(self.left_chunk());
let (bytes_left, bytes_right) = self.split_bytes(byte_offset);
let left_left_summary = M::up_to(
self.left_chunk(),
self.left_summary,
offset,
byte_offset,
);
let left = Self {
bytes: bytes_left,
left_summary: left_left_summary,
len_right: 0,
};
let right = Self {
bytes: bytes_right,
left_summary: self.left_summary - left_left_summary,
len_right: self.len_right,
};
((left, left.left_summary), (right, summary - left.left_summary))
} else {
offset -= self.left_measure::<M>();
let byte_offset = offset.to_byte_offset(self.right_chunk());
let (bytes_left, bytes_right) =
self.split_bytes(self.len_left() + byte_offset);
let right_left_summary = M::up_to(
self.right_chunk(),
self.right_summary(summary),
offset,
byte_offset,
);
let left = Self {
bytes: bytes_left,
left_summary: self.left_summary,
len_right: right_left_summary.bytes() as u16,
};
let right = Self {
bytes: bytes_right,
left_summary: self.right_summary(summary) - right_left_summary,
len_right: 0,
};
((left, summary - right.left_summary), (right, right.left_summary))
}
}
#[inline]
fn split_bytes(&self, byte_offset: usize) -> (&'a [u8], &'a [u8]) {
debug_assert!(byte_offset <= self.len());
use core::cmp::Ordering;
let offset = match byte_offset.cmp(&self.len_left()) {
Ordering::Less => byte_offset,
Ordering::Greater => byte_offset + self.len_gap(),
Ordering::Equal => {
return (
self.left_chunk().as_bytes(),
self.right_chunk().as_bytes(),
);
},
};
self.bytes.split_at(offset)
}
#[inline]
fn summarize_right_chunk(&self) -> ChunkSummary {
ChunkSummary::from(self.right_chunk())
}
}
impl Summarize for GapSlice<'_> {
type Summary = ChunkSummary;
#[inline]
fn summarize(&self) -> Self::Summary {
self.left_summary + self.summarize_right_chunk()
}
}
#[cfg(test)]
mod tests {
use crate::rope::gap_buffer::GapBuffer;
use crate::tree::{AsSlice, Summarize};
#[test]
fn debug_slice() {
let buffer = GapBuffer::<10>::from("Hello");
assert_eq!("\"He~~~~~llo\"", format!("{:?}", buffer.as_slice()));
}
#[test]
fn truncate_trailing_crlf() {
let buffer = GapBuffer::<5>::from("bar\r\n");
let mut slice = buffer.as_slice();
let summary = slice.summarize();
slice.truncate_trailing_line_break(summary);
assert_eq!("bar", slice);
}
#[test]
fn truncate_trailing_lf() {
let buffer = GapBuffer::<5>::from("bar\n");
let mut slice = buffer.as_slice();
let summary = slice.summarize();
slice.truncate_trailing_line_break(summary);
assert_eq!("bar", slice);
}
}