1use std::collections::HashMap;
4
5const MIN_WORD_LENGTH: usize = 3;
6
7#[must_use]
9pub fn is_word_char(character: char) -> bool {
10 character.is_alphanumeric() || character == '_'
11}
12
13#[must_use]
16pub fn collect_words(text: &str) -> Vec<(String, usize)> {
17 let mut counts: HashMap<&str, usize> = HashMap::new();
18 for word in text
19 .split(|c: char| !is_word_char(c))
20 .filter(|word| word.chars().count() >= MIN_WORD_LENGTH)
21 {
22 *counts.entry(word).or_insert(0) += 1;
23 }
24 let mut words: Vec<(String, usize)> = counts
25 .into_iter()
26 .map(|(word, count)| (word.to_owned(), count))
27 .collect();
28 words.sort_unstable_by(|(left_word, left_count), (right_word, right_count)| {
29 right_count
30 .cmp(left_count)
31 .then_with(|| left_word.cmp(right_word))
32 });
33 words
34}
35
36#[must_use]
39pub fn current_prefix(text: &str, cursor: usize) -> String {
40 let before: String = text.chars().take(cursor).collect();
41 let reversed: String = before
42 .chars()
43 .rev()
44 .take_while(|c| is_word_char(*c))
45 .collect();
46 reversed.chars().rev().collect()
47}
48
49#[must_use]
52pub fn current_word(text: &str, cursor: usize) -> (usize, usize) {
53 let chars: Vec<char> = text.chars().collect();
54 let cursor = cursor.min(chars.len());
55 let leading = chars
56 .iter()
57 .take(cursor)
58 .rev()
59 .take_while(|character| is_word_char(**character))
60 .count();
61 let trailing = chars
62 .iter()
63 .skip(cursor)
64 .take_while(|character| is_word_char(**character))
65 .count();
66 (cursor - leading, cursor + trailing)
67}
68
69#[must_use]
75pub fn matches<'a>(words: &'a [(String, usize)], prefix: &str) -> Vec<(&'a String, usize)> {
76 let found: Vec<(&'a String, usize)> = words
77 .iter()
78 .filter(|(word, _)| word.starts_with(prefix))
79 .map(|(word, count)| (word, *count))
80 .collect();
81 if found.iter().all(|(word, _)| word.as_str() == prefix) {
82 return Vec::new();
83 }
84 found
85}
86
87#[must_use]
90pub fn char_to_byte(text: &str, char_index: usize) -> usize {
91 text.char_indices()
92 .nth(char_index)
93 .map_or(text.len(), |(byte, _)| byte)
94}