pub(crate) fn utf16_offset_to_byte(code: &str, target: usize) -> Option<usize> {
let mut utf16 = 0usize;
for (byte_idx, ch) in code.char_indices() {
if utf16 == target {
return Some(byte_idx);
}
if utf16 > target {
return None;
}
utf16 += ch.len_utf16();
}
(utf16 == target).then_some(code.len())
}
pub(crate) fn slice_by_utf16_range(
code: &str,
start_utf16: usize,
end_utf16: usize,
) -> Option<&str> {
if start_utf16 >= end_utf16 {
return None;
}
let start = utf16_offset_to_byte(code, start_utf16)?;
let end = start + utf16_offset_to_byte(&code[start..], end_utf16 - start_utf16)?;
Some(&code[start..end])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ascii_offsets_are_byte_offsets() {
let code = "const setState = 0;";
assert_eq!(utf16_offset_to_byte(code, 0), Some(0));
assert_eq!(utf16_offset_to_byte(code, 6), Some(6));
assert_eq!(slice_by_utf16_range(code, 6, 14), Some("setState"));
}
#[test]
fn bmp_characters_take_three_bytes_and_one_unit() {
let code = "// 日本語\nsetState";
assert_eq!(utf16_offset_to_byte(code, 3), Some(3));
assert_eq!(utf16_offset_to_byte(code, 6), Some(12));
assert_eq!(slice_by_utf16_range(code, 7, 15), Some("setState"));
}
#[test]
fn astral_characters_take_two_units() {
let code = "🎉🎉setState";
assert_eq!(utf16_offset_to_byte(code, 2), Some(4));
assert_eq!(utf16_offset_to_byte(code, 4), Some(8));
assert_eq!(slice_by_utf16_range(code, 4, 12), Some("setState"));
}
#[test]
fn offset_inside_a_surrogate_pair_is_rejected() {
let code = "🎉setState";
assert_eq!(utf16_offset_to_byte(code, 1), None);
assert_eq!(slice_by_utf16_range(code, 1, 10), None);
}
#[test]
fn offset_at_end_of_string_resolves() {
let code = "🎉ab";
assert_eq!(utf16_offset_to_byte(code, 4), Some(code.len()));
assert_eq!(slice_by_utf16_range(code, 2, 4), Some("ab"));
}
#[test]
fn out_of_range_offset_is_rejected() {
let code = "ab";
assert_eq!(utf16_offset_to_byte(code, 3), None);
assert_eq!(slice_by_utf16_range(code, 0, 5), None);
}
#[test]
fn empty_and_inverted_ranges_are_rejected() {
let code = "setState";
assert_eq!(slice_by_utf16_range(code, 3, 3), None);
assert_eq!(slice_by_utf16_range(code, 5, 2), None);
}
}