use std::hash::Hash;
use crate::Rope;
#[cfg(feature = "facet")]
use facet::Facet;
use super::BoundaryError;
use crate::snip::Target;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "facet", derive(Facet))]
#[repr(u8)]
pub enum Extent {
Lines(usize),
Chars(usize),
Bytes(usize),
Matching(usize, Target),
}
pub fn calculate_lines_extent(
rope: &Rope,
from: usize,
count: usize,
) -> Result<usize, BoundaryError> {
let start_line = rope.char_to_line(from);
let target_line = start_line.saturating_add(count);
if target_line >= rope.len_lines() {
return Err(BoundaryError::ExtentOutOfBounds);
}
Ok(rope.line_to_char(target_line))
}
pub fn calculate_chars_extent(
rope: &Rope,
from: usize,
count: usize,
) -> Result<usize, BoundaryError> {
let new_end = from.saturating_add(count);
if new_end > rope.len_chars() {
return Err(BoundaryError::ExtentOutOfBounds);
}
Ok(new_end)
}
pub fn calculate_bytes_extent(
rope: &Rope,
from: usize,
count: usize,
) -> Result<usize, BoundaryError> {
let from_byte = rope.char_to_byte(from);
let new_byte = from_byte.saturating_add(count);
if new_byte > rope.len_bytes() {
return Err(BoundaryError::ExtentOutOfBounds);
}
let char_idx = rope.byte_to_char(new_byte);
let char_start_byte = rope.char_to_byte(char_idx);
if char_start_byte < new_byte {
let next = char_idx.saturating_add(1);
if next >= rope.len_chars() {
return Err(BoundaryError::ExtentOutOfBounds);
}
Ok(next)
} else {
Ok(char_idx)
}
}
pub fn calculate_matching_extent(
rope: &Rope,
from: usize,
count: usize,
target: &Target,
) -> Result<usize, BoundaryError> {
if count == 0 {
return Ok(from);
}
match target {
Target::Literal(needle) if needle.is_empty() => {
return Err(BoundaryError::InvalidExtent);
}
Target::Line(_) | Target::Char(_) | Target::Position { .. } => {
return Err(BoundaryError::InvalidExtent);
}
Target::Literal(_) => {} #[cfg(feature = "regex")]
Target::Pattern(_) => {} }
if from >= rope.len_chars() {
return Err(BoundaryError::ExtentOutOfBounds);
}
let total_chars = rope.len_chars();
let mut remaining = count;
let mut cursor = from;
while remaining > 0 {
if cursor >= total_chars {
return Err(BoundaryError::ExtentOutOfBounds);
}
match target {
Target::Literal(needle) => {
let (chunks_iter, mut chunk_byte_idx, mut chunk_char_idx, _) =
rope.chunks_at_char(cursor);
let mut found = false;
for chunk in chunks_iter {
let local_char_offset = cursor.saturating_sub(chunk_char_idx);
let mut byte_offset_in_chunk = 0usize;
if local_char_offset > 0 {
let mut set = false;
for (reached, (b_idx, _ch)) in chunk.char_indices().enumerate() {
if reached == local_char_offset {
byte_offset_in_chunk = b_idx;
set = true;
break;
}
}
if !set {
byte_offset_in_chunk = chunk.len();
}
}
if byte_offset_in_chunk <= chunk.len() {
if let Some(rel_byte_pos) = chunk[byte_offset_in_chunk..].find(needle) {
let match_end_byte =
chunk_byte_idx + byte_offset_in_chunk + rel_byte_pos + needle.len();
let match_end_char = rope.byte_to_char(match_end_byte);
cursor = match_end_char;
found = true;
break;
}
}
let chunk_char_count = chunk.chars().count();
chunk_char_idx = chunk_char_idx.saturating_add(chunk_char_count);
chunk_byte_idx = chunk_byte_idx.saturating_add(chunk.len());
}
if !found {
return Err(BoundaryError::ExtentOutOfBounds);
}
remaining = remaining.saturating_sub(1);
}
#[cfg(feature = "regex")]
Target::Pattern(pattern) => {
use regex_cursor::{Input as RegexInput, RopeyCursor};
let regex = regex_cursor::engines::meta::Regex::new(pattern)
.map_err(|_| BoundaryError::InvalidExtent)?;
let slice = rope.slice(cursor..);
let cursor_struct = RopeyCursor::new(slice);
let input = RegexInput::new(cursor_struct);
if let Some(m) = regex.find(input) {
cursor = cursor.saturating_add(m.end());
remaining = remaining.saturating_sub(1);
} else {
return Err(BoundaryError::ExtentOutOfBounds);
}
}
_ => unreachable!(), }
}
Ok(cursor)
}
#[cfg(test)]
#[path = "../../../tests/boundary_extent.rs"]
mod boundary_extent;