pub(crate) fn char_len(s: &str) -> usize {
s.chars().count()
}
pub(crate) fn char_index_to_byte_start(s: &str, char_index: usize) -> Option<usize> {
let n = char_len(s);
if char_index > n {
return None;
}
if char_index == n {
return Some(s.len());
}
if char_index == 0 {
return Some(0);
}
s.char_indices()
.nth(char_index)
.map(|(byte_idx, _)| byte_idx)
}
pub(crate) fn char_index_to_byte_end(s: &str, char_index: usize) -> Option<usize> {
char_index_to_byte_start(s, char_index)
}
pub(crate) fn search_byte_range(
s: &str,
pos: usize,
endpos: Option<usize>,
) -> Option<(usize, usize)> {
let char_count = char_len(s);
if pos > char_count {
return None;
}
let end_char = endpos.unwrap_or(char_count);
if end_char > char_count || end_char < pos {
return None;
}
let byte_start = char_index_to_byte_start(s, pos)?;
let byte_end = char_index_to_byte_end(s, end_char)?;
if byte_end < byte_start {
return None;
}
Some((byte_start, byte_end))
}
pub(crate) fn byte_to_char_index(s: &str, byte_offset: usize) -> usize {
if byte_offset == 0 {
return 0;
}
debug_assert!(byte_offset <= s.len());
debug_assert!(s.is_char_boundary(byte_offset));
s[..byte_offset].chars().count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rocket_ab_range() {
let s = "🚀ab value=42";
assert_eq!(char_len(s), 12);
assert_eq!(char_index_to_byte_start(s, 0), Some(0));
assert_eq!(char_index_to_byte_start(s, 1), Some(4)); assert_eq!(search_byte_range(s, 1, None), Some((4, s.len())));
}
#[test]
fn byte_to_char_roundtrip() {
let s = "🚀ab";
assert_eq!(byte_to_char_index(s, 4), 1);
assert_eq!(byte_to_char_index(s, 0), 0);
}
}