1use {
2 crossterm::event::KeyCode,
3 proc_macro::TokenStream as TokenStream1,
4 proc_macro2::{Group, Span, TokenStream},
5 quote::quote,
6 strict::OneToThree,
7 syn::{
8 parse::{Error, Parse, ParseStream, Result},
9 parse_macro_input, Ident, LitChar, LitInt, Token,
10 },
11};
12
13struct KeyCombinationKey {
14 pub crate_path: TokenStream,
15 pub ctrl: bool,
16 pub cmd: bool,
17 pub alt: bool,
18 pub shift: bool,
19 pub codes: OneToThree<TokenStream>,
20}
21
22
23fn parse_key_code(
34 raw: &str,
35 shift: bool,
36 code_span: Span,
37) -> Result<KeyCode> {
38 use KeyCode::*;
39 let code = match raw {
40 "esc" => Esc,
41 "enter" => Enter,
42 "left" => Left,
43 "right" => Right,
44 "up" => Up,
45 "down" => Down,
46 "home" => Home,
47 "end" => End,
48 "pageup" => PageUp,
49 "pagedown" => PageDown,
50 "backtab" => BackTab,
51 "backspace" => Backspace,
52 "del" => Delete,
53 "delete" => Delete,
54 "insert" => Insert,
55 "ins" => Insert,
56 "f1" => F(1),
57 "f2" => F(2),
58 "f3" => F(3),
59 "f4" => F(4),
60 "f5" => F(5),
61 "f6" => F(6),
62 "f7" => F(7),
63 "f8" => F(8),
64 "f9" => F(9),
65 "f10" => F(10),
66 "f11" => F(11),
67 "f12" => F(12),
68 "f13" => F(13),
69 "f14" => F(14),
70 "f15" => F(15),
71 "f16" => F(16),
72 "f17" => F(17),
73 "f18" => F(18),
74 "f19" => F(19),
75 "f20" => F(20),
76 "f21" => F(21),
77 "f22" => F(22),
78 "f23" => F(23),
79 "f24" => F(24),
80 "space" => Char(' '),
81 "hyphen" => Char('-'),
82 "minus" => Char('-'),
83 "tab" => Tab,
84 c if c.chars().count() == 1 => {
85 let mut c = c.chars().next().unwrap();
86 if shift {
87 c = c.to_ascii_uppercase();
88 }
89 Char(c)
90 }
91 _ => {
92 return Err(Error::new(
93 code_span,
94 format_args!("unrecognized key code {raw:?}"),
95 ));
96 }
97 };
98 Ok(code)
99}
100
101
102fn key_code_to_token_stream(key_code: KeyCode, code_span: Span) -> Result<TokenStream> {
103 let ts = match key_code {
104 KeyCode::Backspace => quote! { Backspace },
105 KeyCode::Enter => quote! { Enter },
106 KeyCode::Left => quote! { Left },
107 KeyCode::Right => quote! { Right },
108 KeyCode::Up => quote! { Up },
109 KeyCode::Down => quote! { Down },
110 KeyCode::Home => quote! { Home },
111 KeyCode::End => quote! { End },
112 KeyCode::PageUp => quote! { PageUp },
113 KeyCode::PageDown => quote! { PageDown },
114 KeyCode::Tab => quote! { Tab },
115 KeyCode::BackTab => quote! { BackTab },
116 KeyCode::Delete => quote! { Delete },
117 KeyCode::Insert => quote! { Insert },
118 KeyCode::F(n) => quote! { F(#n) },
119 KeyCode::Char(c) => quote! { Char(#c) },
120 KeyCode::Null => quote! { Null },
121 KeyCode::Esc => quote! { Esc },
122 KeyCode::CapsLock => quote! { CapsLock },
123 KeyCode::ScrollLock => quote! { ScrollLock },
124 KeyCode::NumLock => quote! { NumLock },
125 KeyCode::PrintScreen => quote! { PrintScreen },
126 KeyCode::Pause => quote! { Pause },
127 KeyCode::Menu => quote! { Menu },
128 KeyCode::KeypadBegin => quote! { KeypadBegin },
129 _ => {
132 return Err(Error::new(
133 code_span,
134 format_args!("failed to encode {key_code:?}"),
135 ));
136 }
137 };
138 Ok(ts)
139}
140
141impl Parse for KeyCombinationKey {
142 fn parse(input: ParseStream<'_>) -> Result<Self> {
143 let crate_path = input.parse::<Group>()?.stream();
144
145 let mut ctrl = false;
146 let mut cmd = false;
147 let mut alt = false;
148 let mut shift = false;
149
150 let (code, code_span) = loop {
151 let lookahead = input.lookahead1();
152
153 if lookahead.peek(LitChar) {
154 let lit = input.parse::<LitChar>()?;
155 break (lit.value().to_lowercase().collect(), lit.span());
156 }
157
158 if lookahead.peek(LitInt) {
159 let int = input.parse::<LitInt>()?;
160 let digits = int.base10_digits();
161 if digits.len() > 1 {
162 return Err(Error::new(int.span(), "invalid key; must be between 0-9"));
163 }
164 break (digits.to_owned(), int.span());
165 }
166
167 if !lookahead.peek(Ident) {
168 return Err(lookahead.error());
169 }
170
171 let ident = input.parse::<Ident>()?;
172 let ident_value = ident.to_string().to_lowercase();
173 let modifier = match &*ident_value {
174 "ctrl" => &mut ctrl,
175 "cmd" => &mut cmd,
176 "alt" => &mut alt,
177 "shift" => &mut shift,
178 _ => break (ident_value, ident.span()),
179 };
180 if *modifier {
181 return Err(Error::new(
182 ident.span(),
183 format_args!("duplicate modifier {ident_value}"),
184 ));
185 }
186 *modifier = true;
187
188 input.parse::<Token![-]>()?;
189 };
190
191 let first_code = parse_key_code(&code, shift, code_span)?;
193 let codes = if input.parse::<Token![-]>().is_ok() {
194 let ident = input.parse::<Ident>()?;
195 let second_code = parse_key_code(&ident.to_string().to_lowercase(), shift, ident.span())?;
196 if input.parse::<Token![-]>().is_ok() {
197 let ident = input.parse::<Ident>()?;
198 let third_code = parse_key_code(&ident.to_string().to_lowercase(), shift, ident.span())?;
199 OneToThree::Three(first_code, second_code, third_code)
200 } else {
201 OneToThree::Two(first_code, second_code)
202 }
203 } else {
204 OneToThree::One(first_code)
205 };
206
207 let codes = codes.sorted();
211
212 let codes = codes.try_map(|key_code| key_code_to_token_stream(key_code, input.span()))?;
214
215 Ok(KeyCombinationKey {
216 crate_path,
217 ctrl,
218 cmd,
219 alt,
220 shift,
221 codes,
222 })
223 }
224}
225
226#[doc(hidden)]
228#[proc_macro]
229pub fn key(input: TokenStream1) -> TokenStream1 {
230 let KeyCombinationKey {
231 crate_path,
232 ctrl,
233 cmd,
234 alt,
235 shift,
236 codes,
237 } = parse_macro_input!(input);
238
239 let mut modifier_constant = "MODS".to_owned();
240 if ctrl {
241 modifier_constant.push_str("_CTRL");
242 }
243 if cmd {
244 modifier_constant.push_str("_CMD");
245 }
246 if alt {
247 modifier_constant.push_str("_ALT");
248 }
249 if shift {
250 modifier_constant.push_str("_SHIFT");
251 }
252 let modifier_constant = Ident::new(&modifier_constant, Span::call_site());
253
254 match codes {
255 OneToThree::One(code) => {
256 quote! {
257 #crate_path::KeyCombination {
258 codes: #crate_path::__private::OneToThree::One(
259 #crate_path::__private::crossterm::event::KeyCode::#code
260 ),
261 modifiers: #crate_path::__private::#modifier_constant,
262 }
263 }
264 }
265 OneToThree::Two(a, b) => {
266 quote! {
267 #crate_path::KeyCombination {
268 codes: #crate_path::__private::OneToThree::Two(
269 #crate_path::__private::crossterm::event::KeyCode::#a,
270 #crate_path::__private::crossterm::event::KeyCode::#b,
271 ),
272 modifiers: #crate_path::__private::#modifier_constant,
273 }
274 }
275 }
276 OneToThree::Three(a, b, c) => {
277 quote! {
278 #crate_path::KeyCombination {
279 codes: #crate_path::__private::OneToThree::Three(
280 #crate_path::__private::crossterm::event::KeyCode::#a,
281 #crate_path::__private::crossterm::event::KeyCode::#b,
282 #crate_path::__private::crossterm::event::KeyCode::#c,
283 ),
284 modifiers: #crate_path::__private::#modifier_constant,
285 }
286 }
287 }
288 }
289 .into()
290}