1use crate::event::{Event, parse_event};
4use regex::Regex;
5use std::collections::HashMap;
6use std::sync::LazyLock;
7use tuikit::event::Event as TermEvent;
8use tuikit::key::{Key, from_keyname};
9
10pub type ActionChain = Vec<Event>;
11
12pub struct Input {
13 keymap: HashMap<Key, ActionChain>,
14}
15
16impl Input {
17 pub fn new() -> Self {
18 Input {
19 keymap: get_default_key_map(),
20 }
21 }
22
23 pub fn translate_event(&self, event: TermEvent) -> (Key, ActionChain) {
24 match event {
25 TermEvent::Key(key) => (
27 key,
28 self.keymap.get(&key).cloned().unwrap_or_else(|| {
29 if let Key::Char(ch) = key {
30 vec![Event::EvActAddChar(ch)]
31 } else {
32 vec![Event::EvInputKey(key)]
33 }
34 }),
35 ),
36 TermEvent::Resize { .. } => (Key::Null, vec![Event::EvActRedraw]),
37 _ => (Key::Null, vec![Event::EvInputInvalid]),
38 }
39 }
40
41 pub fn bind(&mut self, key: &str, action_chain: ActionChain) {
42 let key = from_keyname(key);
43 if key.is_none() || action_chain.is_empty() {
44 return;
45 }
46
47 let key = key.unwrap();
48
49 let _ = self.keymap.remove(&key);
51 self.keymap.entry(key).or_insert(action_chain);
52 }
53
54 pub fn parse_keymaps(&mut self, maps: &[&str]) {
55 for &map in maps {
56 self.parse_keymap(map);
57 }
58 }
59
60 pub fn parse_keymap(&mut self, key_action: &str) {
62 debug!("got key_action: {:?}", key_action);
63 for (key, action_chain) in parse_key_action(key_action).into_iter() {
64 debug!("parsed key_action: {:?}: {:?}", key, action_chain);
65 let action_chain = action_chain
66 .into_iter()
67 .filter_map(|(action, arg)| parse_event(action, arg))
68 .collect();
69 self.bind(key, action_chain);
70 }
71 }
72
73 pub fn parse_expect_keys(&mut self, keys: Option<&str>) {
74 if let Some(keys) = keys {
75 for key in keys.split(',') {
76 self.bind(key, vec![Event::EvActAccept(Some(key.to_string()))]);
77 }
78 }
79 }
80}
81
82type KeyActions<'a> = (&'a str, Vec<(&'a str, Option<String>)>);
83
84pub fn parse_key_action(key_action: &str) -> Vec<KeyActions<'_>> {
87 static RE: LazyLock<Regex> = LazyLock::new(|| {
89 Regex::new(
90 r#"(?si)([^:]+?):((?:\+?[a-z-]+?(?:"[^"]*?"|'[^']*?'|\([^\)]*?\)|\[[^\]]*?\]|:[^:]*?)?\s*)+)(?:,|$)"#,
91 )
92 .unwrap()
93 });
94 static RE_BIND: LazyLock<Regex> = LazyLock::new(|| {
96 Regex::new(r#"(?si)([a-z-]+)("[^"]+?"|'[^']+?'|\([^\)]+?\)|\[[^\]]+?\]|:[^:]+?)?(?:\+|$)"#).unwrap()
97 });
98
99 RE.captures_iter(key_action)
100 .map(|caps| {
101 debug!("RE: caps: {:?}", caps);
102 let key = caps.get(1).unwrap().as_str();
103 let actions = RE_BIND
104 .captures_iter(caps.get(2).unwrap().as_str())
105 .map(|caps| {
106 debug!("RE_BIND: caps: {:?}", caps);
107 (
108 caps.get(1).unwrap().as_str(),
109 caps.get(2).map(|s| {
110 let action = s.as_str();
112 if let Some(stripped) = action.strip_prefix(':') {
113 stripped.to_owned()
114 } else {
115 action[1..action.len() - 1].to_string()
116 }
117 }),
118 )
119 })
120 .collect();
121 (key, actions)
122 })
123 .collect()
124}
125
126pub fn parse_action_arg(action_arg: &str) -> Option<Event> {
128 let fake_key_action = format!("fake_key:{}", action_arg);
130 let keys = parse_key_action(&fake_key_action);
132 if keys.is_empty() || keys[0].1.is_empty() {
134 None
135 } else {
136 let (action, new_arg) = keys[0].1[0].clone();
138 parse_event(action, new_arg)
139 }
140}
141
142#[rustfmt::skip]
143fn get_default_key_map() -> HashMap<Key, ActionChain> {
144 let mut ret = HashMap::new();
145 ret.insert(Key::ESC, vec![Event::EvActAbort]);
146 ret.insert(Key::Ctrl('c'), vec![Event::EvActAbort]);
147 ret.insert(Key::Ctrl('g'), vec![Event::EvActAbort]);
148 ret.insert(Key::Enter, vec![Event::EvActAccept(None)]);
149 ret.insert(Key::Left, vec![Event::EvActBackwardChar]);
150 ret.insert(Key::Ctrl('b'), vec![Event::EvActBackwardChar]);
151 ret.insert(Key::Ctrl('h'), vec![Event::EvActBackwardDeleteChar]);
152 ret.insert(Key::Backspace, vec![Event::EvActBackwardDeleteChar]);
153 ret.insert(Key::AltBackspace, vec![Event::EvActBackwardKillWord]);
154 ret.insert(Key::Alt('b'), vec![Event::EvActBackwardWord]);
155 ret.insert(Key::ShiftLeft, vec![Event::EvActBackwardWord]);
156 ret.insert(Key::CtrlLeft, vec![Event::EvActBackwardWord]);
157 ret.insert(Key::Ctrl('a'), vec![Event::EvActBeginningOfLine]);
158 ret.insert(Key::Home, vec![Event::EvActBeginningOfLine]);
159 ret.insert(Key::Ctrl('l'), vec![Event::EvActClearScreen]);
160 ret.insert(Key::Delete, vec![Event::EvActDeleteChar]);
161 ret.insert(Key::Ctrl('d'), vec![Event::EvActDeleteCharEOF]);
162 ret.insert(Key::Ctrl('j'), vec![Event::EvActDown(1)]);
163 ret.insert(Key::Ctrl('n'), vec![Event::EvActDown(1)]);
164 ret.insert(Key::Down, vec![Event::EvActDown(1)]);
165 ret.insert(Key::Ctrl('e'), vec![Event::EvActEndOfLine]);
166 ret.insert(Key::End, vec![Event::EvActEndOfLine]);
167 ret.insert(Key::Ctrl('f'), vec![Event::EvActForwardChar]);
168 ret.insert(Key::Right, vec![Event::EvActForwardChar]);
169 ret.insert(Key::Alt('f'), vec![Event::EvActForwardWord]);
170 ret.insert(Key::CtrlRight, vec![Event::EvActForwardWord]);
171 ret.insert(Key::ShiftRight, vec![Event::EvActForwardWord]);
172 ret.insert(Key::Alt('d'), vec![Event::EvActKillWord]);
173 ret.insert(Key::ShiftUp, vec![Event::EvActPreviewPageUp(1)]);
174 ret.insert(Key::ShiftDown, vec![Event::EvActPreviewPageDown(1)]);
175 ret.insert(Key::PageDown, vec![Event::EvActPageDown(1)]);
176 ret.insert(Key::PageUp, vec![Event::EvActPageUp(1)]);
177 ret.insert(Key::Ctrl('r'), vec![Event::EvActRotateMode]);
178 ret.insert(Key::Alt('h'), vec![Event::EvActScrollLeft(1)]);
179 ret.insert(Key::Alt('l'), vec![Event::EvActScrollRight(1)]);
180 ret.insert(Key::Tab, vec![Event::EvActToggle, Event::EvActDown(1)]);
181 ret.insert(Key::Ctrl('q'), vec![Event::EvActToggleInteractive]);
182 ret.insert(Key::BackTab, vec![Event::EvActToggle, Event::EvActUp(1)]);
183 ret.insert(Key::Ctrl('u'), vec![Event::EvActUnixLineDiscard]);
184 ret.insert(Key::Ctrl('w'), vec![Event::EvActUnixWordRubout]);
185 ret.insert(Key::Ctrl('p'), vec![Event::EvActUp(1)]);
186 ret.insert(Key::Ctrl('k'), vec![Event::EvActUp(1)]);
187 ret.insert(Key::Up, vec![Event::EvActUp(1)]);
188 ret.insert(Key::Ctrl('y'), vec![Event::EvActYank]);
189 ret.insert(Key::Null, vec![Event::EvActAbort]);
190 ret
191}
192
193#[cfg(test)]
194mod test {
195 use super::*;
196
197 #[test]
198 fn execute_should_be_parsed_correctly() {
199 let cmd = "
201 (grep -o '[a-f0-9]\\{7\\}' | head -1 |
202 xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
203 {}
204 FZF-EOF";
205
206 let key_action_str = format!("ctrl-s:toggle-sort,ctrl-m:execute:{},ctrl-t:toggle", cmd);
207
208 let key_action = parse_key_action(&key_action_str);
209 assert_eq!(("ctrl-s", vec![("toggle-sort", None)]), key_action[0]);
210 assert_eq!(("ctrl-m", vec![("execute", Some(cmd.to_string()))]), key_action[1]);
211 assert_eq!(("ctrl-t", vec![("toggle", None)]), key_action[2]);
212
213 let key_action_str = "f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)";
214 let key_action = parse_key_action(key_action_str);
215 assert_eq!(("f1", vec![("execute", Some("less -f {}".to_string()))]), key_action[0]);
216 assert_eq!(
217 ("ctrl-y", vec![("execute-silent", Some("echo {} | pbcopy".to_string()))]),
218 key_action[1]
219 );
220
221 let key_action_str = "enter:execute($EDITOR +{2} {1})";
223 let key_action = parse_key_action(key_action_str);
224 assert_eq!(
225 ("enter", vec![("execute", Some("$EDITOR +{2} {1}".to_string()))]),
226 key_action[0]
227 );
228 }
229
230 #[test]
231 fn action_chain_should_be_parsed() {
232 let key_action = parse_key_action("ctrl-t:toggle+up");
233 assert_eq!(("ctrl-t", vec![("toggle", None), ("up", None)]), key_action[0]);
234
235 let key_action_str = "f1:execute(less -f {}),ctrl-y:execute-silent(echo {} | pbcopy)+abort";
236 let key_action = parse_key_action(key_action_str);
237 assert_eq!(("f1", vec![("execute", Some("less -f {}".to_string()))]), key_action[0]);
238 assert_eq!(
239 (
240 "ctrl-y",
241 vec![
242 ("execute-silent", Some("echo {} | pbcopy".to_string())),
243 ("abort", None)
244 ]
245 ),
246 key_action[1]
247 );
248 }
249}