idet-core 0.1.0

Shared text-editing library for a Micro-like terminal editor and a gedit-like egui GUI
Documentation
//! Word collection and prefix matching for autocomplete.

const MIN_WORD_LENGTH: usize = 3;

/// Returns whether `character` counts as part of a word (alphanumeric or `_`).
#[must_use]
pub fn is_word_char(character: char) -> bool {
    character.is_alphanumeric() || character == '_'
}

/// Collects the unique words of `text` (length ≥ 3), sorted ascending.
#[must_use]
pub fn collect_words(text: &str) -> Vec<String> {
    let mut words: Vec<String> = text
        .split(|c: char| !is_word_char(c))
        .filter(|word| word.chars().count() >= MIN_WORD_LENGTH)
        .map(str::to_owned)
        .collect();
    words.sort_unstable();
    words.dedup();
    words
}

/// Returns the run of word characters ending immediately before `cursor`,
/// where `cursor` is a character index into `text`.
#[must_use]
pub fn current_prefix(text: &str, cursor: usize) -> String {
    let before: String = text.chars().take(cursor).collect();
    let reversed: String = before
        .chars()
        .rev()
        .take_while(|c| is_word_char(*c))
        .collect();
    reversed.chars().rev().collect()
}

/// Returns the character-index range of the word containing `cursor`,
/// scanning both backward and forward from it with [`is_word_char`].
#[must_use]
pub fn current_word(text: &str, cursor: usize) -> (usize, usize) {
    let chars: Vec<char> = text.chars().collect();
    let cursor = cursor.min(chars.len());
    let mut start = cursor;
    while start > 0 && is_word_char(chars[start - 1]) {
        start -= 1;
    }
    let mut end = cursor;
    while end < chars.len() && is_word_char(chars[end]) {
        end += 1;
    }
    (start, end)
}

/// Returns the words that start with `prefix` but are not equal to it.
#[must_use]
pub fn matches<'a>(words: &'a [String], prefix: &str) -> Vec<&'a String> {
    words
        .iter()
        .filter(|word| word.as_str() != prefix && word.starts_with(prefix))
        .collect()
}

/// Converts the character index `char_index` into a byte offset into `text`,
/// returning the text length when the index is past the end.
#[must_use]
pub fn char_to_byte(text: &str, char_index: usize) -> usize {
    text.char_indices()
        .nth(char_index)
        .map_or(text.len(), |(byte, _)| byte)
}