use unicode_segmentation::UnicodeSegmentation;
use crate::document::Buffer;
const GRAPHEME_WINDOW: usize = 32;
pub fn next_grapheme_offset(buf: &Buffer, char_offset: usize) -> usize {
let len = buf.len_chars();
if char_offset >= len {
return len;
}
let end = (char_offset + GRAPHEME_WINDOW).min(len);
let s = buf.rope().slice(char_offset..end).to_string();
match s.graphemes(true).next() {
Some(g) => char_offset + g.chars().count(),
None => char_offset,
}
}
pub fn prev_grapheme_offset(buf: &Buffer, char_offset: usize) -> usize {
if char_offset == 0 {
return 0;
}
let start = char_offset.saturating_sub(GRAPHEME_WINDOW);
let s = buf.rope().slice(start..char_offset).to_string();
match s.graphemes(true).next_back() {
Some(g) => char_offset - g.chars().count(),
None => char_offset.saturating_sub(1),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn b(s: &str) -> Buffer {
Buffer::from_str(s)
}
#[test]
fn next_steps_over_ascii_one_char_at_a_time() {
let buf = b("hi");
assert_eq!(next_grapheme_offset(&buf, 0), 1);
assert_eq!(next_grapheme_offset(&buf, 1), 2);
assert_eq!(next_grapheme_offset(&buf, 2), 2); }
#[test]
fn prev_steps_over_ascii_one_char_at_a_time() {
let buf = b("hi");
assert_eq!(prev_grapheme_offset(&buf, 2), 1);
assert_eq!(prev_grapheme_offset(&buf, 1), 0);
assert_eq!(prev_grapheme_offset(&buf, 0), 0); }
#[test]
fn next_treats_single_codepoint_emoji_as_one_step() {
let buf = b("๐ฅ");
assert_eq!(next_grapheme_offset(&buf, 0), 1);
assert_eq!(prev_grapheme_offset(&buf, 1), 0);
}
#[test]
fn next_treats_zwj_family_as_one_grapheme() {
let buf = b("๐จ\u{200D}๐ฉ\u{200D}๐ง\u{200D}๐ฆx");
assert_eq!(buf.len_chars(), 8);
assert_eq!(next_grapheme_offset(&buf, 0), 7);
assert_eq!(prev_grapheme_offset(&buf, 7), 0);
assert_eq!(next_grapheme_offset(&buf, 7), 8);
}
#[test]
fn next_treats_combining_mark_as_single_grapheme() {
let buf = b("e\u{0301}!");
assert_eq!(buf.len_chars(), 3);
assert_eq!(next_grapheme_offset(&buf, 0), 2);
assert_eq!(prev_grapheme_offset(&buf, 2), 0);
}
#[test]
fn next_treats_skin_tone_modifier_as_one_grapheme() {
let buf = b("๐\u{1F3FD}!");
assert_eq!(next_grapheme_offset(&buf, 0), 2);
assert_eq!(prev_grapheme_offset(&buf, 2), 0);
}
}