1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! Text processing
//!
//! The functionality here is deliberately a quick hack to get things working.
//! Hopefully it can be replaced with a real mark-up processor without too
//! much API breakage.

use smallvec::SmallVec;

use crate::cast::Conv;
use crate::event::Key;
use crate::text::format::{FontToken, FormattableText};
use crate::text::{Effect, EffectFlags};

/// An access key string
///
/// This is a label which supports highlighting of access keys (sometimes called
/// "mnemonics"). This type represents both the
/// displayed text (via [`FormattableText`] implementation)
/// and the shortcut (via [`AccessString::key`]).
///
/// Markup: `&&` translates to `&`; `&x` for any `x` translates to `x` and
/// identifies `x` as an "access key"; this may be drawn underlined and
/// may support keyboard access via e.g. `Alt+X`
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AccessString {
    label: String,
    effects: SmallVec<[Effect<()>; 2]>,
    key: Option<Key>,
}

impl AccessString {
    /// Parse a `&str`
    ///
    /// Since we require `'static` for references and don't yet have
    /// specialisation, this parser always allocates. Prefer to use `from`.
    fn parse(mut s: &str) -> Self {
        let mut buf = String::with_capacity(s.len());
        let mut effects = SmallVec::<[Effect<()>; 2]>::default();
        let mut key = None;

        while let Some(mut i) = s.find('&') {
            buf.push_str(&s[..i]);
            i += "&".len();
            s = &s[i..];
            let mut chars = s.char_indices();

            match chars.next() {
                None => {
                    // Ending with '&' is an error, but we can ignore it
                    s = &s[0..0];
                    break;
                }
                Some((j, c)) => {
                    // TODO(opt): we can simplify if we don't support multiple mnemonic keys
                    let pos = u32::conv(buf.len());
                    buf.push(c);
                    if effects.last().map(|e| e.start == pos).unwrap_or(false) {
                        effects.last_mut().unwrap().flags = EffectFlags::UNDERLINE;
                    } else {
                        effects.push(Effect {
                            start: pos,
                            flags: EffectFlags::UNDERLINE,
                            aux: (),
                        });
                    }
                    if key.is_none() {
                        let mut kbuf = [0u8; 4];
                        let s = c.to_ascii_lowercase().encode_utf8(&mut kbuf);
                        key = Some(Key::Character(s.into()));
                    }
                    let i = c.len_utf8();
                    s = &s[i..];

                    if let Some((k, _)) = chars.next() {
                        effects.push(Effect {
                            start: pos + u32::conv(k - j),
                            flags: EffectFlags::empty(),
                            aux: (),
                        });
                    }
                }
            }
        }
        buf.push_str(s);
        AccessString {
            label: buf,
            effects,
            key,
        }
    }

    /// Get the key binding, if any
    pub fn key(&self) -> Option<&Key> {
        self.key.as_ref()
    }

    /// Get the text
    pub fn text(&self) -> &str {
        &self.label
    }
}

impl FormattableText for AccessString {
    type FontTokenIter<'a> = std::iter::Empty<FontToken>;

    #[inline]
    fn as_str(&self) -> &str {
        &self.label
    }

    #[inline]
    fn font_tokens(&self, _: f32) -> Self::FontTokenIter<'_> {
        std::iter::empty()
    }

    fn effect_tokens(&self) -> &[Effect<()>] {
        &self.effects
    }
}

impl From<String> for AccessString {
    fn from(input: String) -> Self {
        if input.as_bytes().contains(&b'&') {
            Self::parse(&input)
        } else {
            // fast path: we can use the raw input
            AccessString {
                label: input,
                ..Default::default()
            }
        }
    }
}

impl From<&str> for AccessString {
    fn from(input: &str) -> Self {
        Self::parse(input)
    }
}

impl<T: Into<AccessString> + Copy> From<&T> for AccessString {
    fn from(input: &T) -> Self {
        (*input).into()
    }
}