Skip to main content

rumdl_lib/utils/
mkdocs_icons.rs

1/// MkDocs emoji and icon shortcode detection.
2///
3/// Supports the MkDocs Material emoji/icons extension and standard GitHub-style
4/// emoji shortcodes, exposing just enough surface for `skip_context` to
5/// determine whether a given position falls inside one.
6///
7/// ## References
8///
9/// - [MkDocs Material Icons](https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/)
10/// - [Python-Markdown Emoji](https://facelessuser.github.io/pymdown-extensions/extensions/emoji/)
11use regex::Regex;
12use std::sync::LazyLock;
13
14/// Pattern to match MkDocs icon shortcodes like `:material-check:`,
15/// `:octicons-mark-github-16:`, or `:fontawesome-brands-github:`.
16static ICON_SHORTCODE_PATTERN: LazyLock<Regex> =
17    LazyLock::new(|| Regex::new(r":([a-z][a-z0-9_]*(?:-[a-z0-9_]+)+):").unwrap());
18
19/// Pattern to match standard emoji shortcodes like `:smile:` or `:+1:`.
20static EMOJI_SHORTCODE_PATTERN: LazyLock<Regex> = LazyLock::new(|| Regex::new(r":([a-zA-Z0-9_+-]+):").unwrap());
21
22/// Check if a position in a line is within any emoji/icon shortcode.
23pub fn is_in_any_shortcode(line: &str, position: usize) -> bool {
24    if !line.contains(':') {
25        return false;
26    }
27
28    for m in ICON_SHORTCODE_PATTERN.find_iter(line) {
29        if m.start() <= position && position < m.end() {
30            return true;
31        }
32    }
33
34    for m in EMOJI_SHORTCODE_PATTERN.find_iter(line) {
35        if m.start() <= position && position < m.end() {
36            return true;
37        }
38    }
39
40    false
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn test_is_in_any_shortcode_emoji() {
49        let line = ":smile: and :material-check:";
50        assert!(is_in_any_shortcode(line, 0));
51        assert!(is_in_any_shortcode(line, 3));
52        assert!(is_in_any_shortcode(line, 6));
53    }
54
55    #[test]
56    fn test_is_in_any_shortcode_between() {
57        let line = ":smile: and :material-check:";
58        assert!(!is_in_any_shortcode(line, 7));
59        assert!(!is_in_any_shortcode(line, 10));
60    }
61
62    #[test]
63    fn test_is_in_any_shortcode_icon() {
64        let line = ":smile: and :material-check:";
65        assert!(is_in_any_shortcode(line, 12));
66        assert!(is_in_any_shortcode(line, 20));
67    }
68
69    #[test]
70    fn test_is_in_any_shortcode_no_colon() {
71        assert!(!is_in_any_shortcode("plain text", 3));
72    }
73}