panache_parser/parser/inlines/
emoji.rs1use crate::syntax::SyntaxKind;
2use rowan::GreenNodeBuilder;
3
4pub(crate) fn try_parse_emoji(text: &str) -> Option<(usize, &str)> {
8 let bytes = text.as_bytes();
9 if bytes.len() < 3 || bytes[0] != b':' {
10 return None;
11 }
12
13 let mut end = 1;
14 while end < bytes.len() {
15 let ch = bytes[end] as char;
16 if ch == ':' {
17 break;
18 }
19 if !ch.is_ascii_alphanumeric() && ch != '_' && ch != '+' && ch != '-' {
20 return None;
21 }
22 end += 1;
23 }
24
25 if end >= bytes.len() || bytes[end] != b':' || end == 1 {
26 return None;
27 }
28
29 if end + 1 < bytes.len() {
31 let next = bytes[end + 1] as char;
32 if next.is_ascii_alphanumeric() || next == '_' {
33 return None;
34 }
35 }
36
37 Some((end + 1, &text[1..end]))
38}
39
40pub(crate) fn emit_emoji(builder: &mut GreenNodeBuilder, raw: &str) {
41 builder.start_node(SyntaxKind::EMOJI.into());
42 builder.token(SyntaxKind::TEXT.into(), raw);
43 builder.finish_node();
44}
45
46#[cfg(test)]
47mod tests {
48 use super::try_parse_emoji;
49
50 #[test]
51 fn parses_simple_alias() {
52 let parsed = try_parse_emoji(":smile:");
53 assert_eq!(parsed, Some((7, "smile")));
54 }
55
56 #[test]
57 fn parses_plus_one_alias() {
58 let parsed = try_parse_emoji(":+1:");
59 assert_eq!(parsed, Some((4, "+1")));
60 }
61
62 #[test]
63 fn rejects_spaces_inside_alias() {
64 assert!(try_parse_emoji(":not valid:").is_none());
65 }
66}