#![doc = simple_mermaid::mermaid!("../diagrams/buffer_structure.mmd")]
use crate::{Character, State, Word};
#[derive(Debug, Clone)]
pub struct Buffer {
characters: Vec<Character>,
words: Vec<Word>,
char_to_word_index: Vec<Option<usize>>,
}
impl Buffer {
pub fn new(string: &str) -> Option<Self> {
if string.is_empty() {
return None;
}
let mut buffer = Self {
characters: vec![],
words: vec![],
char_to_word_index: vec![],
};
buffer.push_string(string);
Some(buffer)
}
pub fn text_len(&self) -> usize {
self.characters.len()
}
pub fn get_character(&self, index: usize) -> Option<&Character> {
self.characters.get(index)
}
pub fn current_character(&self, input_len: usize) -> Option<&Character> {
self.characters
.get(input_len)
.or_else(|| self.characters.last())
}
pub fn get_word_containing(&self, char_index: usize) -> Option<&Word> {
let word_index = self.char_to_word_index.get(char_index).copied().flatten()?;
self.words.get(word_index)
}
pub fn get_word_containing_mut(&mut self, char_index: usize) -> Option<&mut Word> {
let word_index = self.char_to_word_index.get(char_index).copied().flatten()?;
self.words.get_mut(word_index)
}
pub fn get_character_mut(&mut self, index: usize) -> Option<&mut Character> {
self.characters.get_mut(index)
}
pub fn get_word_characters(&self, word: &Word) -> &[Character] {
&self.characters[word.start..word.end]
}
pub fn get_word(&self, index: usize) -> Option<&Word> {
self.words.get(index)
}
pub fn word_count(&self) -> usize {
self.words.len()
}
pub fn get_word_index_at(&self, char_index: usize) -> Option<usize> {
self.char_to_word_index.get(char_index).copied().flatten()
}
fn allocate_capacity(&mut self, char_count: usize, word_count: usize) {
self.characters.reserve(char_count);
self.words.reserve(word_count);
self.char_to_word_index.reserve(char_count);
}
fn process_character(
&mut self,
char: char,
index: usize,
original_len: usize,
current_word_start: &mut Option<usize>,
current_word_index: &mut Option<usize>,
) {
let is_whitespace = char.is_ascii_whitespace();
if let Some(word_start) = current_word_start.take_if(|_| is_whitespace) {
self.add_word(word_start, index, original_len);
*current_word_index = None;
} else if !is_whitespace && current_word_start.is_none() {
*current_word_start = Some(index);
*current_word_index = Some(self.words.len()); }
self.characters.push(Character {
char,
state: State::default(),
});
if let Some(word_idx) = *current_word_index {
self.char_to_word_index.push(Some(word_idx));
} else {
self.char_to_word_index.push(None);
}
}
fn add_word(&mut self, word_start: usize, word_end: usize, original_len: usize) {
self.words.push(Word {
start: word_start + original_len,
end: (word_end + original_len).saturating_sub(1),
state: State::default(),
});
}
fn finalize_last_word(
&mut self,
current_word_start: Option<usize>,
chars: &[char],
original_len: usize,
) {
if let Some(word_start) = current_word_start {
let char_count = chars.len();
self.words.push(Word {
start: word_start + original_len,
end: char_count + original_len - 1,
state: State::default(),
});
}
}
pub fn push_string(&mut self, string: &str) {
let mut current_word_start: Option<usize> = None;
let mut current_word_index: Option<usize> = None;
let chars: Vec<char> = string.chars().collect();
let word_count = string.split_ascii_whitespace().count();
let char_count = chars.len();
let original_len = self.characters.len();
self.allocate_capacity(char_count, word_count);
for (index, char) in chars.iter().enumerate() {
self.process_character(
*char,
index,
original_len,
&mut current_word_start,
&mut current_word_index,
);
}
self.finalize_last_word(current_word_start, &chars, original_len);
}
pub fn update_word_state_incrementally(
&mut self,
char_index: usize,
new_character_state: State,
) {
let Some(word_index) = self.char_to_word_index.get(char_index).copied().flatten() else {
return;
};
let Some(word) = self.words.get_mut(word_index) else {
return;
};
if new_character_state > word.state {
word.state = new_character_state;
return;
}
if new_character_state < word.state {
let word_start = word.start;
let word_end = word.end;
let has_character_at_current_state = self.characters[word_start..word_end]
.iter()
.enumerate()
.any(|(i, char)| word_start + i != char_index && char.state == word.state);
if !has_character_at_current_state {
self.recalculate_word_state(word_index);
}
}
}
fn recalculate_word_state(&mut self, word_index: usize) {
let Some(word) = self.words.get_mut(word_index) else {
return;
};
let word_characters = &self.characters[word.start..word.end];
let mut state = State::None;
for character in word_characters {
if character.state > state {
state = character.state;
}
}
word.state = state;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_buffer_word_boundaries() {
let mut text_buffer = Buffer::new("first word").unwrap();
assert_eq!(text_buffer.words.len(), 2);
assert_eq!(text_buffer.words[0].start, 0);
assert_eq!(text_buffer.words[0].end, 4);
assert_eq!(text_buffer.words[1].start, 6);
assert_eq!(text_buffer.words[1].end, 9);
text_buffer.push_string(" second word");
assert_eq!(text_buffer.text_len(), 22);
assert_eq!(text_buffer.words.len(), 4);
assert_eq!(text_buffer.words[2].start, 11);
assert_eq!(text_buffer.words[2].end, 16);
assert_eq!(text_buffer.words[3].start, 18);
assert_eq!(text_buffer.words[3].end, 21);
}
}