#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub enum Bias {
Left,
Right,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct Point {
pub row: u32,
pub col: u32,
}
impl Point {
#[must_use]
pub const fn new(row: u32, col: u32) -> Self {
Self { row, col }
}
pub const ZERO: Self = Self { row: 0, col: 0 };
}
#[must_use]
pub(crate) fn snap_char_boundary(text: &str, offset: u32, bias: Bias) -> u32 {
let len = text.len() as u32;
if offset >= len {
return len;
}
let mut o = offset as usize;
if text.is_char_boundary(o) {
return offset;
}
match bias {
Bias::Left => {
while !text.is_char_boundary(o) {
o -= 1;
}
}
Bias::Right => {
while !text.is_char_boundary(o) {
o += 1;
}
}
}
o as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn points_order_row_major() {
assert!(Point::new(0, 5) < Point::new(1, 0));
assert!(Point::new(1, 2) < Point::new(1, 3));
assert_eq!(Point::ZERO, Point::new(0, 0));
}
#[test]
fn snap_is_identity_on_ascii_and_ends() {
let s = "hello";
for o in 0..=s.len() as u32 {
assert_eq!(snap_char_boundary(s, o, Bias::Left), o);
assert_eq!(snap_char_boundary(s, o, Bias::Right), o);
}
}
#[test]
fn snap_moves_off_multibyte_interior() {
let s = "aé";
assert_eq!(s.len(), 3);
assert_eq!(snap_char_boundary(s, 2, Bias::Left), 1);
assert_eq!(snap_char_boundary(s, 2, Bias::Right), 3);
assert_eq!(snap_char_boundary(s, 1, Bias::Left), 1);
assert_eq!(snap_char_boundary(s, 3, Bias::Right), 3);
}
}