#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RoundDirection {
Backward,
Forward,
}
pub fn snap_to_boundary(input: &str, offset: usize, direction: RoundDirection) -> usize {
if offset >= input.len() {
return input.len();
}
if input.is_char_boundary(offset) {
return offset;
}
match direction {
RoundDirection::Backward => {
(0..offset)
.rev()
.find(|&i| input.is_char_boundary(i))
.unwrap_or(0)
}
RoundDirection::Forward => {
((offset + 1)..=input.len())
.find(|&i| input.is_char_boundary(i))
.unwrap_or(input.len())
}
}
}
pub fn step_char(input: &str, offset: usize, direction: RoundDirection) -> usize {
match direction {
RoundDirection::Forward => {
if offset >= input.len() {
return input.len();
}
let next = offset + 1;
if next >= input.len() {
input.len()
} else {
snap_to_boundary(input, next, RoundDirection::Forward)
}
}
RoundDirection::Backward => {
if offset == 0 {
return 0;
}
let target = offset.saturating_sub(1);
snap_to_boundary(input, target, RoundDirection::Backward)
}
}
}
#[inline]
pub fn safe_increment_offset(input: &str, offset: usize) -> usize {
step_char(input, offset, RoundDirection::Forward)
}
#[inline]
pub fn safe_decrement_offset(input: &str, offset: usize) -> usize {
step_char(input, offset, RoundDirection::Backward)
}
#[inline]
pub fn ensure_char_boundary(input: &str, offset: usize) -> usize {
snap_to_boundary(input, offset, RoundDirection::Backward)
}
#[inline]
pub fn ensure_char_boundary_forward(input: &str, offset: usize) -> usize {
snap_to_boundary(input, offset, RoundDirection::Forward)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_snap_backward_on_boundary() {
let input = "hello";
assert_eq!(snap_to_boundary(input, 0, RoundDirection::Backward), 0);
assert_eq!(snap_to_boundary(input, 3, RoundDirection::Backward), 3);
assert_eq!(snap_to_boundary(input, 5, RoundDirection::Backward), 5);
}
#[test]
fn test_snap_backward_mid_char() {
let input = "😀"; assert_eq!(snap_to_boundary(input, 1, RoundDirection::Backward), 0);
assert_eq!(snap_to_boundary(input, 2, RoundDirection::Backward), 0);
assert_eq!(snap_to_boundary(input, 3, RoundDirection::Backward), 0);
}
#[test]
fn test_snap_forward_on_boundary() {
let input = "hello";
assert_eq!(snap_to_boundary(input, 0, RoundDirection::Forward), 0);
assert_eq!(snap_to_boundary(input, 3, RoundDirection::Forward), 3);
assert_eq!(snap_to_boundary(input, 5, RoundDirection::Forward), 5);
}
#[test]
fn test_snap_forward_mid_char() {
let input = "😀"; assert_eq!(snap_to_boundary(input, 1, RoundDirection::Forward), 4);
assert_eq!(snap_to_boundary(input, 2, RoundDirection::Forward), 4);
assert_eq!(snap_to_boundary(input, 3, RoundDirection::Forward), 4);
}
#[test]
fn test_snap_beyond_input() {
let input = "hello";
assert_eq!(snap_to_boundary(input, 100, RoundDirection::Backward), 5);
assert_eq!(snap_to_boundary(input, 100, RoundDirection::Forward), 5);
}
#[test]
fn test_step_forward() {
let input = "a😀b"; assert_eq!(step_char(input, 0, RoundDirection::Forward), 1); assert_eq!(step_char(input, 1, RoundDirection::Forward), 5); assert_eq!(step_char(input, 5, RoundDirection::Forward), 6); assert_eq!(step_char(input, 6, RoundDirection::Forward), 6); }
#[test]
fn test_step_backward() {
let input = "a😀b"; assert_eq!(step_char(input, 6, RoundDirection::Backward), 5); assert_eq!(step_char(input, 5, RoundDirection::Backward), 1); assert_eq!(step_char(input, 1, RoundDirection::Backward), 0); assert_eq!(step_char(input, 0, RoundDirection::Backward), 0); }
#[test]
fn test_safe_decrement_ascii() {
let input = "hello";
assert_eq!(safe_decrement_offset(input, 5), 4);
assert_eq!(safe_decrement_offset(input, 1), 0);
assert_eq!(safe_decrement_offset(input, 0), 0);
}
#[test]
fn test_safe_decrement_emoji() {
let input = "😀"; assert_eq!(safe_decrement_offset(input, 4), 0);
assert_eq!(safe_decrement_offset(input, 3), 0);
assert_eq!(safe_decrement_offset(input, 2), 0);
assert_eq!(safe_decrement_offset(input, 1), 0);
}
#[test]
fn test_safe_decrement_mixed() {
let input = "a😀b"; assert_eq!(safe_decrement_offset(input, 6), 5);
assert_eq!(safe_decrement_offset(input, 5), 1);
assert_eq!(safe_decrement_offset(input, 1), 0);
}
#[test]
fn test_safe_decrement_with_newline() {
let input = "😀\n"; assert_eq!(safe_decrement_offset(input, 5), 4);
let input2 = "a\n";
assert_eq!(safe_decrement_offset(input2, 2), 1);
}
#[test]
fn test_ensure_boundary() {
let input = "😀";
assert_eq!(ensure_char_boundary(input, 0), 0);
assert_eq!(ensure_char_boundary(input, 1), 0);
assert_eq!(ensure_char_boundary(input, 2), 0);
assert_eq!(ensure_char_boundary(input, 3), 0);
assert_eq!(ensure_char_boundary(input, 4), 4);
}
#[test]
fn test_ensure_boundary_forward() {
let input = "😀"; assert_eq!(ensure_char_boundary_forward(input, 0), 0);
assert_eq!(ensure_char_boundary_forward(input, 1), 4);
assert_eq!(ensure_char_boundary_forward(input, 2), 4);
assert_eq!(ensure_char_boundary_forward(input, 3), 4);
assert_eq!(ensure_char_boundary_forward(input, 4), 4);
}
#[test]
fn test_ensure_boundary_forward_mixed() {
let input = "a😀b"; assert_eq!(ensure_char_boundary_forward(input, 0), 0);
assert_eq!(ensure_char_boundary_forward(input, 1), 1);
assert_eq!(ensure_char_boundary_forward(input, 2), 5);
assert_eq!(ensure_char_boundary_forward(input, 3), 5);
assert_eq!(ensure_char_boundary_forward(input, 4), 5);
assert_eq!(ensure_char_boundary_forward(input, 5), 5);
assert_eq!(ensure_char_boundary_forward(input, 6), 6);
}
#[test]
fn test_ensure_boundary_forward_beyond_input() {
let input = "hello";
assert_eq!(ensure_char_boundary_forward(input, 100), 5);
}
}