scout/
common.rs

1//! Set of common types used through the app
2
3use async_std::sync::Arc;
4use std::fmt;
5use std::slice::Iter;
6use std::time::Instant;
7use unicode_segmentation::UnicodeSegmentation;
8
9pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
10
11/// The Prompt represents the current query, the cursor position in that query and when it was
12/// updated.
13///
14/// When the query in the prompt changes the timestamp is updated to reflect that is a fresh query.
15/// This is then used to print to the UI only latest changes.
16#[derive(Debug, Clone)]
17pub struct Prompt {
18    query: Vec<char>,
19    cursor: usize,
20    timestamp: Instant,
21}
22
23impl Prompt {
24    pub fn add(&mut self, ch: char) {
25        self.query.insert(self.cursor, ch);
26        self.cursor += 1;
27        self.refresh();
28    }
29
30    pub fn backspace(&mut self) -> bool {
31        if self.cursor > 0 {
32            self.cursor -= 1;
33            self.query.remove(self.cursor);
34            self.refresh();
35
36            return true;
37        }
38
39        false
40    }
41
42    pub fn clear(&mut self) {
43        self.query.clear();
44        self.cursor = 0;
45        self.refresh();
46    }
47
48    pub fn left(&mut self) {
49        if self.cursor > 0 {
50            self.cursor -= 1;
51        }
52    }
53
54    pub fn right(&mut self) {
55        if self.cursor < self.len() {
56            self.cursor += 1;
57        }
58    }
59
60    pub fn cursor_at_end(&mut self) {
61        self.cursor = self.len();
62    }
63
64    pub fn cursor_at_start(&mut self) {
65        self.cursor = 0;
66    }
67
68    pub fn cursor_until_end(&self) -> usize {
69        if self.len() < self.cursor {
70            0
71        } else {
72            self.len() - self.cursor
73        }
74    }
75
76    pub fn as_string(&self) -> String {
77        self.query.iter().collect()
78    }
79
80    pub fn timestamp(&self) -> Instant {
81        self.timestamp
82    }
83
84    pub fn len(&self) -> usize {
85        self.query.len()
86    }
87
88    pub fn is_empty(&self) -> bool {
89        self.query.is_empty()
90    }
91
92    pub fn refresh(&mut self) {
93        self.timestamp = Instant::now();
94    }
95}
96
97impl From<&String> for Prompt {
98    fn from(string: &String) -> Self {
99        let query = string.chars().collect::<Vec<char>>();
100        let cursor = query.len();
101
102        Self {
103            query,
104            cursor,
105            ..Default::default()
106        }
107    }
108}
109
110impl Default for Prompt {
111    fn default() -> Self {
112        Self {
113            timestamp: Instant::now(),
114            cursor: 0,
115            query: vec![],
116        }
117    }
118}
119
120/// The Arc version of Letters
121pub type Text = Arc<Letters>;
122
123/// Text type builder
124#[derive(Debug, Clone)]
125pub struct TextBuilder;
126
127impl TextBuilder {
128    pub fn build(string: &str) -> Text {
129        let text: Letters = string.into();
130
131        Arc::new(text)
132    }
133}
134
135/// The collection of letters (Graphemes) of a string.
136///
137/// These letters are the core part of the fuzzy matching algorithm.
138///
139/// This type is not used directly but through the Text type,
140/// which is an Arc wrapper around this type. We use Arc to reduce
141/// the String allocations between tasks as much as possible.
142#[derive(Debug, Clone)]
143pub struct Letters {
144    string: String,
145    graphemes: Vec<String>,
146    graphemes_lw: Vec<String>,
147}
148
149impl Letters {
150    pub fn new(string: String) -> Self {
151        let graphemes = string.graphemes(true).map(String::from).collect::<Vec<_>>();
152
153        let graphemes_lw = graphemes
154            .iter()
155            .map(|s| s.to_lowercase())
156            .collect::<Vec<_>>();
157
158        Self {
159            string,
160            graphemes,
161            graphemes_lw,
162        }
163    }
164
165    pub fn len(&self) -> usize {
166        self.graphemes.len()
167    }
168
169    pub fn last_index(&self) -> usize {
170        let len = self.len();
171
172        if len == 0 {
173            0
174        } else {
175            len - 1
176        }
177    }
178
179    pub fn grapheme_at(&self, index: usize) -> &'_ str {
180        &self.graphemes[index]
181    }
182
183    pub fn lowercase_grapheme_at(&self, index: usize) -> &'_ str {
184        &self.graphemes_lw[index]
185    }
186
187    pub fn iter(&self) -> Iter<'_, String> {
188        self.graphemes.iter()
189    }
190
191    pub fn lowercase_iter(&self) -> Iter<'_, String> {
192        self.graphemes_lw.iter()
193    }
194
195    pub fn is_empty(&self) -> bool {
196        self.string.is_empty()
197    }
198}
199
200impl From<&str> for Letters {
201    fn from(string: &str) -> Self {
202        Self::new(String::from(string))
203    }
204}
205
206impl From<String> for Letters {
207    fn from(string: String) -> Self {
208        Self::new(string)
209    }
210}
211
212impl fmt::Display for Letters {
213    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214        write!(f, "{}", self.string)
215    }
216}