1#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
6pub struct EmojiCategorySnapshot {
7 pub name: String,
8 pub emojis: Vec<String>,
9}
10
11#[derive(uniffi::Record, Clone, Debug, PartialEq, Eq)]
12pub struct EmojiAliasSnapshot {
13 pub emoji: String,
14 pub keywords: String,
15}
16
17fn cat(name: &str, emojis: &[&str]) -> EmojiCategorySnapshot {
18 EmojiCategorySnapshot {
19 name: name.to_string(),
20 emojis: emojis.iter().map(|s| (*s).to_string()).collect(),
21 }
22}
23
24fn alias(emoji: &str, keywords: &str) -> EmojiAliasSnapshot {
25 EmojiAliasSnapshot {
26 emoji: emoji.to_string(),
27 keywords: keywords.to_string(),
28 }
29}
30
31#[uniffi::export]
32pub fn iris_emoji_catalog() -> Vec<EmojiCategorySnapshot> {
33 vec![
34 cat(
35 "Smileys",
36 &[
37 "๐",
38 "๐",
39 "๐",
40 "๐",
41 "๐",
42 "๐
",
43 "๐คฃ",
44 "๐",
45 "๐",
46 "๐",
47 "๐ซ ",
48 "๐",
49 "๐",
50 "๐",
51 "๐ฅฐ",
52 "๐",
53 "๐คฉ",
54 "๐",
55 "๐",
56 "โบ๏ธ",
57 "๐",
58 "๐",
59 "๐ฅฒ",
60 "๐",
61 "๐",
62 "๐",
63 "๐คช",
64 "๐",
65 "๐ค",
66 "๐ค",
67 "๐คญ",
68 "๐ซข",
69 "๐ซฃ",
70 "๐คซ",
71 "๐ค",
72 "๐ซก",
73 "๐ค",
74 "๐คจ",
75 "๐",
76 "๐",
77 "๐ถ",
78 "๐ซฅ",
79 "๐ถโ๐ซ๏ธ",
80 "๐",
81 "๐",
82 "๐",
83 "๐ฌ",
84 "๐ฎโ๐จ",
85 "๐คฅ",
86 "๐",
87 "๐",
88 "๐ช",
89 "๐คค",
90 "๐ด",
91 "๐ท",
92 "๐ค",
93 "๐ค",
94 "๐คข",
95 "๐คฎ",
96 "๐คง",
97 "๐ฅต",
98 "๐ฅถ",
99 "๐ฅด",
100 "๐ต",
101 "๐ตโ๐ซ",
102 "๐คฏ",
103 "๐ค ",
104 "๐ฅณ",
105 "๐ฅธ",
106 "๐",
107 "๐ค",
108 "๐ง",
109 "๐",
110 "๐ซค",
111 "๐",
112 "๐",
113 "โน๏ธ",
114 "๐ฎ",
115 "๐ฏ",
116 "๐ฒ",
117 "๐ณ",
118 "๐ฅบ",
119 "๐ฅน",
120 "๐ฆ",
121 "๐ง",
122 "๐จ",
123 "๐ฐ",
124 "๐ฅ",
125 "๐ข",
126 "๐ญ",
127 "๐ฑ",
128 "๐",
129 "๐ฃ",
130 "๐",
131 "๐",
132 "๐ฉ",
133 "๐ซ",
134 "๐ฅฑ",
135 "๐ค",
136 "๐ก",
137 "๐ ",
138 "๐คฌ",
139 "๐",
140 "๐ฟ",
141 "๐",
142 "โ ๏ธ",
143 "๐ฉ",
144 "๐คก",
145 "๐น",
146 "๐บ",
147 "๐ป",
148 "๐ฝ",
149 "๐พ",
150 "๐ค",
151 "๐บ",
152 "๐ธ",
153 "๐น",
154 "๐ป",
155 "๐ผ",
156 "๐ฝ",
157 "๐",
158 "๐ฟ",
159 "๐พ",
160 ],
161 ),
162 cat(
163 "Hearts",
164 &[
165 "โค๏ธ",
166 "๐งก",
167 "๐",
168 "๐",
169 "๐",
170 "๐",
171 "๐ค",
172 "๐ค",
173 "๐ค",
174 "๐",
175 "๐",
176 "๐",
177 "๐",
178 "๐",
179 "๐",
180 "๐",
181 "๐",
182 "โฅ๏ธ",
183 "๐",
184 "โฃ๏ธ",
185 "โค๏ธโ๐ฅ",
186 "โค๏ธโ๐ฉน",
187 ],
188 ),
189 cat(
190 "Hands",
191 &[
192 "๐", "๐", "๐", "๐ค", "๐ค", "โ๏ธ", "๐ค", "๐ซฐ", "๐ค", "๐ค", "๐ค", "๐", "๐", "๐",
193 "๐", "โ๏ธ", "โ", "๐ค", "๐", "๐", "๐", "๐ค", "๐", "๐", "๐", "๐ซถ", "๐ช", "๐ซต",
194 "๐ซฑ", "๐ซฒ",
195 ],
196 ),
197 cat(
198 "Animals",
199 &[
200 "๐ถ", "๐ฑ", "๐ญ", "๐น", "๐ฐ", "๐ฆ", "๐ป", "๐ผ", "๐จ", "๐ฏ", "๐ฆ", "๐ฎ", "๐ท", "๐ธ",
201 "๐ต", "๐", "๐", "๐", "๐", "๐ง", "๐ฆ", "๐ฆ
", "๐ฆ", "๐ฆ", "๐", "๐ฆ", "๐", "๐ข",
202 "๐", "๐ฆ", "๐", "๐ฆ", "๐ฌ", "๐ณ", "๐ฆ",
203 ],
204 ),
205 cat(
206 "Food",
207 &[
208 "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐ซ", "๐", "๐", "๐ฅญ", "๐",
209 "๐ฅฅ", "๐ฅ", "๐
", "๐ฅ", "๐ฅ", "๐ฝ", "๐", "๐ฅ", "๐", "๐", "๐", "๐ญ", "๐ฟ", "๐ฅช",
210 "๐ฎ", "๐ฏ", "๐ฃ", "๐", "๐", "๐ฆ", "๐ฉ", "๐ช", "๐", "๐ฐ", "โ", "๐ต", "๐บ", "๐ฅ",
211 "๐ท", "๐ฅ",
212 ],
213 ),
214 cat(
215 "Activities",
216 &[
217 "โฝ", "๐", "๐", "โพ", "๐ฅ", "๐พ", "๐", "๐", "๐ฑ", "๐ช", "๐", "๐ธ", "๐ฅ
", "๐",
218 "๐", "๐ฅ", "๐", "๐ช", "๐ฅ", "๐ฅ", "๐ฝ", "โธ", "๐ฅ", "๐ท", "๐ช", "๐๏ธ", "๐คธ", "๐คบ",
219 "๐", "โท", "๐", "๐๏ธ", "๐", "๐ฃ", "๐", "๐คฝ", "๐ด", "๐ต", "๐ฏ", "๐ฎ", "๐ฒ", "๐ผ",
220 "๐ค", "๐ง", "๐ท", "๐ธ", "๐ฅ",
221 ],
222 ),
223 cat(
224 "Travel",
225 &[
226 "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐ป", "๐", "๐", "๐",
227 "๐ต", "๐", "๐บ", "๐ฒ", "๐ด", "๐น", "๐", "โ๏ธ", "๐", "๐ธ", "๐ถ", "โต", "๐ข", "๐",
228 "๐บ", "๐ฝ", "๐ผ", "๐ฐ", "๐ก", "๐ข", "๐ ", "๐", "๐", "๐", "๐", "๐", "๐", "๐
",
229 "๐",
230 ],
231 ),
232 cat(
233 "Objects",
234 &[
235 "๐ฑ", "๐ป", "โจ๏ธ", "๐ฅ", "๐จ", "๐ฑ", "๐พ", "๐ฟ", "๐ท", "๐ธ", "๐น", "๐ฅ", "๐บ", "๐ป",
236 "๐", "โ๏ธ", "๐", "๐", "๐ก", "๐ฆ", "๐ฏ", "๐งฏ", "๐ข", "๐ต", "๐ฐ", "๐ณ", "๐", "โ๏ธ",
237 "๐ง", "๐จ", "๐ ", "โ", "๐ช", "๐ช", "๐ฉ", "โ๏ธ", "๐งฑ", "โ", "๐งฒ", "๐ซ", "๐ฃ", "๐งจ",
238 ],
239 ),
240 cat(
241 "Symbols",
242 &[
243 "โ
", "โ", "โ๏ธ", "โ", "โญ", "๐ซ", "โ ๏ธ", "๐ฑ", "โ๏ธ", "๐ฏ", "๐ฅ", "โจ", "๐", "โญ",
244 "๐", "โ๏ธ", "๐", "โก", "โ๏ธ", "๐ฅ", "๐", "๐ง", "๐ฆ", "๐", "๐", "๐", "๐", "๐",
245 "๐ช
", "๐พ", "๐ฅ", "๐ฅ", "๐ฅ", "๐", "๐", "๐
", "๐ค", "๐ญ", "๐ฏ", "๐ฌ", "๐", "๐",
246 "๐", "๐", "๐", "๐", "โป๏ธ", "โฎ๏ธ", "โ๏ธ", "โช๏ธ", "๐", "โธ๏ธ", "โก๏ธ", "โฏ๏ธ", "โฆ๏ธ",
247 ],
248 ),
249 ]
250}
251
252#[uniffi::export]
253pub fn iris_emoji_search_aliases() -> Vec<EmojiAliasSnapshot> {
254 let search_aliases = vec![
255 alias("๐", "laugh laughing lol haha"),
256 alias("๐คฃ", "laugh laughing lol haha rolling"),
257 alias("๐", "smile smiling happy"),
258 alias("๐", "smile smiling happy"),
259 alias("๐", "love heart eyes"),
260 alias("๐ฅฐ", "love hearts"),
261 alias("๐", "kiss love"),
262 alias("๐ข", "sad tear crying"),
263 alias("๐ญ", "sad cry crying"),
264 alias("๐ ", "angry mad"),
265 alias("๐คฌ", "angry mad swearing"),
266 alias("๐", "pray praying thanks thank you please"),
267 alias("๐", "clap applause"),
268 alias("๐", "hooray yay hands"),
269 alias("โค๏ธ", "love heart red"),
270 alias("โฅ๏ธ", "love heart red"),
271 alias("๐ฅ", "fire lit hot"),
272 alias("๐", "party celebrate celebration"),
273 alias("๐", "party celebrate celebration"),
274 alias("โจ", "sparkle sparkles"),
275 alias("โ
", "yes check done"),
276 alias("โ", "no cross x"),
277 alias("๐", "eyes look watching"),
278 alias("๐ฏ", "hundred perfect"),
279 alias("๐ฎ", "wow surprised shock shocked open mouth"),
280 alias("๐ฏ", "wow surprised hushed"),
281 alias("๐ฒ", "wow surprised astonished shock"),
282 alias("๐คฏ", "mind blown shock wow"),
283 alias("๐ฑ", "scream shock shocked wow"),
284 alias("๐ค", "thinking hmm think"),
285 alias("๐คข", "sick gross nauseated"),
286 alias("๐คฎ", "sick gross vomit puke"),
287 alias("๐ฅฑ", "yawn yawning tired bored"),
288 alias("๐ด", "sleep sleeping tired"),
289 alias("๐ค", "nerd nerdy glasses"),
290 alias("๐ง", "monocle inspect curious"),
291 alias("๐", "skull dead"),
292 alias("๐ป", "ghost spooky"),
293 alias("๐คก", "clown"),
294 alias("๐ฝ", "alien"),
295 alias("๐ค", "robot bot"),
296 alias("๐ฉ", "poo poop"),
297 alias("๐", "devil smiling devil"),
298 alias("๐", "thumbs up like yes"),
299 alias("๐", "thumbs down dislike no"),
300 alias("๐ฅบ", "pleading puppy eyes"),
301 ];
302 include_str!("../assets/emoji-shortcodes.tsv")
305 .lines()
306 .filter_map(|line| line.split_once('\t'))
307 .map(|(emoji, shortcodes)| {
308 let extra = search_aliases.iter().find(|entry| entry.emoji == emoji);
309 let keywords = match extra {
310 Some(entry) => format!("{shortcodes} {}", entry.keywords),
311 None => shortcodes.to_string(),
312 };
313 EmojiAliasSnapshot {
314 emoji: emoji.to_string(),
315 keywords,
316 }
317 })
318 .collect()
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 #[test]
326 fn catalog_contains_wow_emoji() {
327 let catalog = iris_emoji_catalog();
328 let smileys = catalog
329 .iter()
330 .find(|c| c.name == "Smileys")
331 .expect("Smileys category");
332 assert!(
333 smileys.emojis.iter().any(|e| e == "๐ฎ"),
334 "Smileys must include the open-mouth (wow) emoji"
335 );
336 }
337
338 #[test]
339 fn aliases_route_wow_to_open_mouth() {
340 let aliases = iris_emoji_search_aliases();
341 let hit = aliases
342 .iter()
343 .find(|a| a.emoji == "๐ฎ")
344 .expect("alias entry for open-mouth emoji");
345 assert!(hit.keywords.contains("wow"));
346 }
347
348 #[test]
349 fn every_picker_emoji_has_a_unique_completion_label() {
350 let aliases = iris_emoji_search_aliases();
351 let mut labels = std::collections::HashSet::new();
352 for emoji in iris_emoji_catalog().into_iter().flat_map(|c| c.emojis) {
353 let entry = aliases.iter().find(|entry| entry.emoji == emoji).unwrap();
354 let label = entry.keywords.split_whitespace().next().unwrap();
355 assert!(labels.insert(label), "duplicate shortcode: {label}");
356 assert!(label
357 .chars()
358 .all(|c| c.is_ascii_alphanumeric() || "_+-".contains(c)));
359 }
360 assert_eq!(labels.len(), aliases.len());
361 }
362
363 #[test]
364 fn familiar_shortcodes_keep_the_same_emoji_on_every_platform() {
365 let aliases = iris_emoji_search_aliases();
366 for (emoji, expected) in [
367 ("๐", "smile"),
368 ("๐", "joy"),
369 ("โค๏ธ", "heart"),
370 ("๐", "thumbsup"),
371 ("๐ฅ", "fire"),
372 ] {
373 let entry = aliases.iter().find(|entry| entry.emoji == emoji).unwrap();
374 assert_eq!(entry.keywords.split_whitespace().next(), Some(expected));
375 }
376 assert!(aliases
377 .iter()
378 .find(|entry| entry.emoji == "๐")
379 .unwrap()
380 .keywords
381 .split_whitespace()
382 .any(|word| word == "+1"));
383 }
384}