Skip to main content

below_view/controllers/
controller_infra.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17/// Convert a string to cursive Key enum
18fn str_to_key(cmd: &str) -> Option<Key> {
19    match cmd.trim().to_lowercase().as_str() {
20        "tab" => Some(Key::Tab),
21        "enter" => Some(Key::Enter),
22        "backspace" => Some(Key::Backspace),
23        "left" => Some(Key::Left),
24        "right" => Some(Key::Right),
25        "up" => Some(Key::Up),
26        "down" => Some(Key::Down),
27        "ins" => Some(Key::Ins),
28        "del" => Some(Key::Del),
29        "home" => Some(Key::Home),
30        "end" => Some(Key::End),
31        "page_up" => Some(Key::PageUp),
32        "page_down" => Some(Key::PageDown),
33        "pause_break" => Some(Key::PauseBreak),
34        "esc" => Some(Key::Esc),
35        _ => None,
36    }
37}
38
39fn key_to_str(key: &Key) -> &'static str {
40    match key {
41        Key::Tab => "Tab",
42        Key::Enter => "Enter",
43        Key::Backspace => "Backspace",
44        Key::Left => "Left",
45        Key::Right => "Right",
46        Key::Up => "Up",
47        Key::Down => "Down",
48        Key::Ins => "Ins",
49        Key::Del => "Del",
50        Key::Home => "Home",
51        Key::End => "End",
52        Key::PageUp => "PageUp",
53        Key::PageDown => "PageDown",
54        Key::PauseBreak => "PauseBreak",
55        Key::Esc => "Esc",
56        _ => "Unknown",
57    }
58}
59
60/// Convert a command to Cursive event.
61// This fn is used while parsing the user's cmdrc file and generate
62// a customized command-event map
63pub fn str_to_event(cmd: &str) -> Option<Event> {
64    if cmd.len() == 1 {
65        return Some(Event::Char(
66            cmd.chars()
67                .next()
68                .expect("Failed to parse first char from command"),
69        ));
70    }
71
72    let cmd_vec = cmd.split('-').collect::<Vec<&str>>();
73    match cmd_vec.len() {
74        1 => str_to_key(cmd_vec[0]).map(Event::Key),
75        2 => match cmd_vec[0].trim().to_lowercase().as_str() {
76            "ctrl" if cmd_vec[1].len() == 1 => Some(Event::CtrlChar(
77                cmd_vec[1]
78                    .chars()
79                    .next()
80                    .expect("Failed to parse first char from ctrl-command"),
81            )),
82            "alt" if cmd_vec[1].len() == 1 => Some(Event::AltChar(
83                cmd_vec[1]
84                    .chars()
85                    .next()
86                    .expect("Failed to parse first char from alt-command"),
87            )),
88            "shift" => str_to_key(cmd_vec[1]).map(Event::Shift),
89            "alt" => str_to_key(cmd_vec[1]).map(Event::Alt),
90            "altshift" => str_to_key(cmd_vec[1]).map(Event::AltShift),
91            "ctrl" => str_to_key(cmd_vec[1]).map(Event::Ctrl),
92            "ctrlshift" => str_to_key(cmd_vec[1]).map(Event::CtrlShift),
93            "ctrlalt" => str_to_key(cmd_vec[1]).map(Event::CtrlAlt),
94            _ => None,
95        },
96        _ => None,
97    }
98}
99
100pub fn event_to_string(event: &Event) -> String {
101    match event {
102        Event::Char(c) => format!("'{}'", c),
103        Event::CtrlChar(c) => format!("<Ctrl> '{}'", c),
104        Event::AltChar(c) => format!("<Alt> '{}'", c),
105        Event::Key(key) => format!("<{}>", key_to_str(key)),
106        Event::Shift(key) => format!("<Shift><{}>", key_to_str(key)),
107        Event::Alt(key) => format!("<Alt><{}>", key_to_str(key)),
108        Event::AltShift(key) => format!("<Alt><Shift><{}>", key_to_str(key)),
109        Event::Ctrl(key) => format!("<Ctrl><{}>", key_to_str(key)),
110        Event::CtrlShift(key) => format!("<Ctrl><Shift><{}>", key_to_str(key)),
111        Event::CtrlAlt(key) => format!("<Ctrl><Alt><{}>", key_to_str(key)),
112        _ => "Unknown".into(),
113    }
114}
115
116/// Common trait that each controller should implement, more details in the module
117/// level doc.
118pub trait EventController {
119    /// Return the command for this controller
120    fn command() -> &'static str;
121
122    // A short version of cmd
123    fn cmd_shortcut() -> &'static str;
124
125    /// Return the Event trigger for this controller
126    fn default_events() -> Vec<Event>;
127
128    /// Handler for event, for event that don't need a cursive object
129    fn handle<T: 'static + ViewBridge>(_view: &mut StatsView<T>, _cmd_vec: &[&str]) {}
130
131    /// Callback for event, for event that need a cursive object
132    fn callback<T: 'static + ViewBridge>(_c: &mut Cursive, _cmd_vec: &[&str]) {}
133}
134
135/// Macro to make view event controller
136/// # Argument
137/// * name - Struct name
138/// * cmd - command string
139/// * cmd_short - command shortcut string, empty string "" means no need for short cut
140/// * event - event trigger. Custom values in eventrc will be added to command map
141/// * handle - handler closure for view level processing
142/// * callback - callback closure for cursive level processing
143macro_rules! make_event_controller {
144    ($name:ident, $cmd:expr, $cmd_short:expr, $event:expr, $handle:expr) => {
145        pub struct $name;
146
147        impl EventController for $name {
148            fn command() -> &'static str {
149                $cmd
150            }
151
152            fn cmd_shortcut() -> &'static str {
153                $cmd_short
154            }
155
156            fn default_events() -> Vec<Event> {
157                $event
158            }
159
160            fn handle<T: 'static + ViewBridge>(view: &mut StatsView<T>, cmd_vec: &[&str]) {
161                $handle(view, cmd_vec)
162            }
163        }
164    };
165
166    ($name:ident, $cmd:expr, $cmd_short:expr, $event:expr, $handle:expr, $callback:expr) => {
167        pub struct $name;
168
169        impl EventController for $name {
170            fn command() -> &'static str {
171                $cmd
172            }
173
174            fn cmd_shortcut() -> &'static str {
175                $cmd_short
176            }
177
178            fn default_events() -> Vec<Event> {
179                $event
180            }
181
182            fn handle<T: 'static + ViewBridge>(view: &mut StatsView<T>, cmd_vec: &[&str]) {
183                $handle(view, cmd_vec)
184            }
185
186            fn callback<T: 'static + ViewBridge>(c: &mut Cursive, cmd_vec: &[&str]) {
187                $callback(c, cmd_vec)
188            }
189        }
190    };
191}
192
193/// Generate controller enums with its building functions.
194// The controllers enum will map the enum member to the actual controller
195// struct that implement the EventController trait.
196macro_rules! make_controllers {
197    ($($(#[$attr:meta])* $enum_item:ident: $struct_item:ident,)*) => {
198        #[derive(Clone, PartialEq, Debug, Hash, Eq)]
199        pub enum Controllers {
200            Unknown,
201            $(
202                $(#[$attr])*
203                $enum_item,
204            )*
205        }
206
207        impl Controllers {
208            pub fn command(&self) -> &'static str {
209                match self {
210                    Controllers::Unknown => "",
211                    $(
212                        $(#[$attr])*
213                        Controllers::$enum_item => $struct_item::command(),
214                    )*
215                }
216            }
217
218            pub fn cmd_shortcut(&self) -> &'static str {
219                match self {
220                    Controllers::Unknown => "",
221                    $(
222                        $(#[$attr])*
223                        Controllers::$enum_item => $struct_item::cmd_shortcut(),
224                    )*
225                }
226            }
227
228            pub fn default_events(&self) -> Vec<Event> {
229                match self {
230                    Controllers::Unknown => vec![Event::Unknown(vec![])],
231                    $(
232                        $(#[$attr])*
233                        Controllers::$enum_item => $struct_item::default_events(),
234                    )*
235                }
236            }
237
238            pub fn handle<T: 'static + ViewBridge>(&self, view: &mut StatsView<T>, cmd_vec: &[&str]) {
239                match self {
240                    Controllers::Unknown => (),
241                    $(
242                        $(#[$attr])*
243                        Controllers::$enum_item => $struct_item::handle(view, cmd_vec),
244                    )*
245                }
246            }
247
248            pub fn callback<T: 'static + ViewBridge>(&self, c: &mut Cursive, cmd_vec: &[&str]) {
249                match self {
250                    Controllers::Unknown => (),
251                    $(
252                        $(#[$attr])*
253                        Controllers::$enum_item => $struct_item::callback::<T>(c, cmd_vec),
254                    )*
255                }
256            }
257        }
258
259        fn insert_event_string(c: &mut Cursive, res: &mut HashMap<Event, Controllers>, table: &toml::value::Table,
260            event_str: &str, controller: &Controllers) {
261            match (str_to_event(event_str)) {
262                Some(event) => {
263                    match res.get(&event) {
264                        // If we are replacing the keybinding for a pre-existing command, don't replace the key binding
265                        // unless the belowrc also remaps the command to a new key.
266                        Some(existing_controller) if !table.contains_key(existing_controller.command()) => {
267                            view_warn!(
268                                c,
269                                "Event {} has been used by: {}",
270                                event_str,
271                                existing_controller.command()
272                            );
273                        }
274                        _ => {
275                            res.insert(event, controller.clone());
276                        }
277                    }
278                },
279                None => {
280                    view_warn!(c, "Fail to parse command from cmdrc: {} --> {}", controller.command(), event_str);
281                }
282            }
283        }
284
285        /// Map the controller enum to event trigger
286        pub fn make_event_controller_map(c: &mut Cursive, cmdrc: &Option<Value>) -> HashMap<Event, Controllers> {
287            let mut res: HashMap<Event, Controllers> = HashMap::new();
288
289            // Generate default hashmap
290            $(
291                for event in $struct_item::default_events() {
292                    $(#[$attr])*
293                    res.insert(
294                        event,
295                        Controllers::$enum_item
296                    );
297                }
298            )*
299
300            // Replace value with cmdrc
301            cmdrc.as_ref().map(|value| {
302                let cmd_controllers = c
303                    .user_data::<crate::ViewState>()
304                    .expect("No user data set")
305                    .cmd_controllers
306                    .clone();
307
308                value.as_table().map(|table| table.iter().for_each(|(k, v)| {
309                    match (cmd_controllers.borrow().get::<str>(k), v.as_array()) {
310                        (Some(controller), Some(key_array)) => {
311                            for event_item in key_array.iter() {
312                                match event_item.as_str() {
313                                    Some(event_str) => insert_event_string(
314                                            c,
315                                            &mut
316                                            res,
317                                            table,
318                                            event_str,
319                                            controller
320                                        ),
321                                    None => view_warn!(c, "Unknown entry in event list {}", event_item),
322                                }
323                            }
324                        },
325                        (Some(controller), None) => {
326                            // Single item key bindings are not arrays but strings, try it as a String
327                            match (v.as_str()) {
328                                Some(event_str) => insert_event_string(c, &mut res, table, event_str, controller),
329                                None => {
330                                    view_warn!(c, "Could not parse key binding {} for command {}", k, v);
331                                }
332                            }
333                        },
334                        (None, _) => {
335                            view_warn!(c, "Unrecogonized command: {}", k);
336                        }
337                    }
338                }));
339            });
340
341            res
342        }
343
344        /// Map the controller enum to cmd string
345        pub fn make_cmd_controller_map() -> HashMap<&'static str, Controllers> {
346            let mut res = HashMap::new();
347            $(
348                $(#[$attr])*
349                res.insert(
350                    $struct_item::command(),
351                    Controllers::$enum_item
352                );
353
354                $(#[$attr])*
355                if !$struct_item::cmd_shortcut().is_empty() {
356                    res.insert(
357                        $struct_item::cmd_shortcut(),
358                        Controllers::$enum_item
359                    );
360                }
361            )*
362            res
363        }
364    }
365}