#[inline]
pub fn utf16_len(text: &str) -> usize {
if text.is_ascii() {
text.len()
} else {
text.chars().map(char::len_utf16).sum()
}
}
#[inline]
pub(super) fn prefix_len(text: &str, bytes: usize) -> usize {
let end = bytes.min(text.len());
let end = (end..=text.len())
.find(|at| text.is_char_boundary(*at))
.unwrap_or(text.len());
utf16_len(text.get(..end).unwrap_or(text))
}
#[inline]
pub fn utf16_offset(text: &str, column: u32) -> Option<usize> {
let column = column as usize;
let prefix = text.as_bytes().get(..column)?;
if prefix.is_ascii() {
return Some(column);
}
let mut units = 0;
for (at, ch) in text.char_indices() {
if units == column {
return Some(at);
}
units += ch.len_utf16();
if units > column {
return None;
}
}
(units == column).then_some(text.len())
}
#[cfg(test)]
mod tests {
use super::utf16_offset;
#[test]
fn every_utf16_boundary_matches_the_authored_characters() {
for source in ["", "abc", "ascii😀é€", "😀ascii", "é😀x"] {
let mut column = 0;
for (byte, ch) in source.char_indices() {
assert_eq!(utf16_offset(source, column), Some(byte));
if ch.len_utf16() == 2 {
assert_eq!(utf16_offset(source, column + 1), None);
}
column += ch.len_utf16() as u32;
}
assert_eq!(utf16_offset(source, column), Some(source.len()));
assert_eq!(utf16_offset(source, column + 1), None);
assert_eq!(utf16_offset(source, u32::MAX), None);
}
}
}