pub fn floor_char_boundary(text: &str, index: usize) -> usize {
let len = text.len();
if index >= len {
return len;
}
let bytes = text.as_bytes();
let mut boundary = index;
while boundary > 0 && bytes[boundary] & 0xC0 == 0x80 {
boundary -= 1;
}
boundary
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_boundaries_are_identity() {
let text = "hello";
for i in 0..=text.len() {
assert_eq!(floor_char_boundary(text, i), i);
}
}
#[test]
fn index_past_the_end_clamps_to_the_length() {
assert_eq!(floor_char_boundary("hello", 5), 5);
assert_eq!(floor_char_boundary("hello", 99), 5);
assert_eq!(floor_char_boundary("", 0), 0);
assert_eq!(floor_char_boundary("", 7), 0);
}
#[test]
fn interior_indices_land_on_a_real_boundary() {
let text = "aé世b";
assert_eq!(text.len(), 7);
for i in 0..=text.len() {
let boundary = floor_char_boundary(text, i);
assert!(boundary <= i, "must round down, got {boundary} for {i}");
assert!(text.is_char_boundary(boundary));
}
assert_eq!(floor_char_boundary(text, 2), 1); assert_eq!(floor_char_boundary(text, 3), 3); assert_eq!(floor_char_boundary(text, 5), 3); assert_eq!(floor_char_boundary(text, 6), 6); }
#[test]
fn astral_characters_are_not_split() {
let text = "🎉";
assert_eq!(text.len(), 4);
for i in 0..4 {
assert_eq!(floor_char_boundary(text, i), 0, "offset {i} is inside the emoji");
}
assert_eq!(floor_char_boundary(text, 4), 4);
}
#[test]
fn every_prefix_of_a_multibyte_string_is_sliceable() {
let text = "héllo wörld 世界 🎉";
for i in 0..=text.len() + 3 {
let _ = &text[..floor_char_boundary(text, i)];
}
}
}