Repository: osa1/tiny (language: rust)

Issue:
Function-keys (F1-F12) support
It may be possible to capture these keys correctly and be able to bind them to `KeyActions`

> But we can't map the functional keys (F1...F12...) right now, am I correct?

_Originally posted by @097115 in https://github.com/osa1/tiny/issues/328#issuecomment-873522382_


Discussion / hints:

Fix patch:
diff --git a/crates/libtiny_tui/src/key_map.rs b/crates/libtiny_tui/src/key_map.rs
index 9b571163..75d50e5c 100644
--- a/crates/libtiny_tui/src/key_map.rs
+++ b/crates/libtiny_tui/src/key_map.rs
@@ -3,7 +3,7 @@ use std::fmt::Display;
 
 use serde::de::{MapAccess, Unexpected, Visitor};
 use serde::{Deserialize, Deserializer};
-use term_input::{Arrow, Key};
+use term_input::{Arrow, FKey, Key};
 
 #[derive(Debug, PartialEq)]
 pub(crate) struct KeyMap(HashMap<Key, KeyAction>);
@@ -111,8 +111,8 @@ impl Default for KeyMap {
             (Key::Ctrl('d'), KeyAction::MessagesPageDown),
             (Key::PageUp, KeyAction::MessagesPageUp),
             (Key::PageDown, KeyAction::MessagesPageDown),
-            (Key::ShiftUp, KeyAction::MessagesScrollUp),
-            (Key::ShiftDown, KeyAction::MessagesScrollDown),
+            (Key::ShiftArrow(Arrow::Up), KeyAction::MessagesScrollUp),
+            (Key::ShiftArrow(Arrow::Down), KeyAction::MessagesScrollDown),
             (Key::Home, KeyAction::MessagesScrollTop),
             (Key::End, KeyAction::MessagesScrollBottom),
             (Key::Tab, KeyAction::InputAutoComplete),
@@ -201,10 +201,10 @@ impl<'de> Deserialize<'de> for MappedKey {
             type Value = MappedKey;
 
             fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
-                write!(formatter, "single keys: backspace, del, end, esc, home, pgdown, pgup, tab, up, down, left right, [a-z], [0-9]. ")?;
+                write!(formatter, "single keys: backspace, del, end, esc, home, pgdown, pgup, tab, up, down, left right, [a-z], [0-9], [f1-f12]. ")?;
                 write!(
                     formatter,
-                    "modifiers with arrow key or single characters:  alt, shift, ctrl"
+                    "modifiers with an arrow key, function key, or single characters:  alt, shift, ctrl"
                 )
             }
 
@@ -232,31 +232,49 @@ impl<'de> Deserialize<'de> for MappedKey {
                         "pgdown" => Key::PageDown,
                         "pgup" => Key::PageUp,
                         "tab" => Key::Tab,
-                        "up" => Key::Arrow(Arrow::Up),
-                        "down" => Key::Arrow(Arrow::Down),
-                        "left" => Key::Arrow(Arrow::Left),
-                        "right" => Key::Arrow(Arrow::Right),
-                        ch => Key::Char(single_key(ch)?),
+                        other => {
+                            if let Some(arrow) = parse_arrow(other) {
+                                Key::Arrow(arrow)
+                            } else if let Some(f_key) = parse_f_key(other) {
+                                Key::FKey(f_key)
+                            } else {
+                                Key::Char(single_key(other)?)
+                            }
+                        }
                     },
                     Some((k1, k2)) => match k1 {
                         "alt" => match k2 {
-                            "up" => Key::AltArrow(Arrow::Up),
-                            "down" => Key::AltArrow(Arrow::Down),
-                            "left" => Key::AltArrow(Arrow::Left),
-                            "right" => Key::AltArrow(Arrow::Right),
-                            ch => Key::AltChar(single_key(ch)?),
+                            other => {
+                                if let Some(arrow) = parse_arrow(other) {
+                                    Key::AltArrow(arrow)
+                                } else if let Some(f_key) = parse_f_key(other) {
+                                    Key::AltF(f_key)
+                                } else {
+                                    Key::AltChar(single_key(other)?)
+                                }
+                            }
                         },
                         "ctrl" => match k2 {
-                            "up" => Key::CtrlArrow(Arrow::Up),
-                            "down" => Key::CtrlArrow(Arrow::Down),
-                            "left" => Key::CtrlArrow(Arrow::Left),
-                            "right" => Key::CtrlArrow(Arrow::Right),
-                            ch => Key::Ctrl(single_key(ch)?),
+                            other => {
+                                if let Some(arrow) = parse_arrow(other) {
+                                    Key::CtrlArrow(arrow)
+                                } else if let Some(f_key) = parse_f_key(other) {
+                                    Key::CtrlF(f_key)
+                                } else {
+                                    Key::Ctrl(single_key(other)?)
+                                }
+                            }
                         },
                         "shift" => match k2 {
-                            "up" => Key::ShiftUp,
-                            "down" => Key::ShiftDown,
-                            unexp => return Err(E::invalid_value(Unexpected::Str(unexp), &Self)),
+                            other => {
+                                if let Some(arrow) = parse_arrow(other) {
+                                    Key::ShiftArrow(arrow)
+                                } else if let Some(f_key) = parse_f_key(other) {
+                                    Key::ShiftF(f_key)
+                                } else {
+                                    return Err(E::invalid_value(Unexpected::Str(other), &Self));
+                                }
+                            }
                         },
                         unexp => return Err(E::invalid_value(Unexpected::Str(unexp), &Self)),
                     },
@@ -274,6 +292,34 @@ impl<'de> Deserialize<'de> for MappedKey {
     }
 }
 
+fn parse_arrow(s: &str) -> Option<Arrow> {
+    match s {
+        "up" => Some(Arrow::Up),
+        "down" => Some(Arrow::Down),
+        "left" => Some(Arrow::Left),
+        "right" => Some(Arrow::Right),
+        _ => None,
+    }
+}
+
+fn parse_f_key(s: &str) -> Option<FKey> {
+    match s {
+        "f1" => Some(FKey::F1),
+        "f2" => Some(FKey::F2),
+        "f3" => Some(FKey::F3),
+        "f4" => Some(FKey::F4),
+        "f5" => Some(FKey::F5),
+        "f6" => Some(FKey::F6),
+        "f7" => Some(FKey::F7),
+        "f8" => Some(FKey::F8),
+        "f9" => Some(FKey::F9),
+        "f10" => Some(FKey::F10),
+        "f11" => Some(FKey::F11),
+        "f12" => Some(FKey::F12),
+        _ => None,
+    }
+}
+
 #[test]
 fn deser_key() {
     let s = "alt_p";
@@ -285,6 +331,21 @@ fn deser_key() {
     let s = "alt__";
     let key: MappedKey = serde_yaml::from_str(s).unwrap();
     assert_eq!(Key::AltChar('_'), key.0);
+    let s = "ctrl_/";
+    let key: MappedKey = serde_yaml::from_str(s).unwrap();
+    assert_eq!(Key::Ctrl('/'), key.0);
+    let s = "ctrl_f2";
+    let key: MappedKey = serde_yaml::from_str(s).unwrap();
+    assert_eq!(Key::CtrlF(FKey::F2), key.0);
+    let s = "alt_f2";
+    let key: MappedKey = serde_yaml::from_str(s).unwrap();
+    assert_eq!(Key::AltF(FKey::F2), key.0);
+    let s = "shift_f2";
+    let key: MappedKey = serde_yaml::from_str(s).unwrap();
+    assert_eq!(Key::ShiftF(FKey::F2), key.0);
+    let s = "shift_left";
+    let key: MappedKey = serde_yaml::from_str(s).unwrap();
+    assert_eq!(Key::ShiftArrow(Arrow::Left), key.0);
 }
 
 #[test]
diff --git a/crates/term_input/src/lib.rs b/crates/term_input/src/lib.rs
index 6b86ab7e..3a96abbf 100644
--- a/crates/term_input/src/lib.rs
+++ b/crates/term_input/src/lib.rs
@@ -32,19 +32,22 @@ extern crate log;
 pub enum Key {
     AltArrow(Arrow),
     AltChar(char),
+    AltF(FKey),
     Arrow(Arrow),
     Backspace,
     Char(char),
     Ctrl(char),
     CtrlArrow(Arrow),
+    CtrlF(FKey),
     Del,
     End,
     Esc,
+    FKey(FKey),
     Home,
     PageDown,
     PageUp,
-    ShiftDown,
-    ShiftUp,
+    ShiftArrow(Arrow),
+    ShiftF(FKey),
     Tab,
 }
 
@@ -56,6 +59,22 @@ pub enum Arrow {
     Down,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq)]
+pub enum FKey {
+    F1,
+    F2,
+    F3,
+    F4,
+    F5,
+    F6,
+    F7,
+    F8,
+    F9,
+    F10,
+    F11,
+    F12,
+}
+
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum Event {
     /// A single key input.
@@ -85,6 +104,18 @@ byte_seq_parser! {
     [27, 91, 49, 59, 51, 68] => Key::AltArrow(Arrow::Left),
     [27, 91, 49, 59, 51, 67] => Key::AltArrow(Arrow::Right),
     [27, 91, 49, 59, 51, 65] => Key::AltArrow(Arrow::Up),
+    [27, 91, 49, 59, 51, 80] => Key::AltF(FKey::F1),
+    [27, 91, 49, 59, 51, 81] => Key::AltF(FKey::F2),
+    [27, 91, 49, 59, 51, 82] => Key::AltF(FKey::F3),
+    [27, 91, 49, 59, 51, 83] => Key::AltF(FKey::F4),
+    [27, 91, 49, 53, 59, 51, 126] => Key::AltF(FKey::F5),
+    [27, 91, 49, 55, 59, 51, 126] => Key::AltF(FKey::F6),
+    [27, 91, 49, 56, 59, 51, 126] => Key::AltF(FKey::F7),
+    [27, 91, 49, 57, 59, 51, 126] => Key::AltF(FKey::F8),
+    [27, 91, 50, 48, 59, 51, 126] => Key::AltF(FKey::F9),
+    [27, 91, 50, 49, 59, 51, 126] => Key::AltF(FKey::F10),
+    [27, 91, 50, 51, 59, 51, 126] => Key::AltF(FKey::F11),
+    [27, 91, 50, 52, 59, 51, 126] => Key::AltF(FKey::F12),
     [27, 91, 66] => Key::Arrow(Arrow::Down),
     [27, 79, 66] => Key::Arrow(Arrow::Down),
     [27, 91, 68] => Key::Arrow(Arrow::Left),
@@ -97,16 +128,54 @@ byte_seq_parser! {
     [27, 91, 49, 59, 53, 68] => Key::CtrlArrow(Arrow::Left),
     [27, 91, 49, 59, 53, 67] => Key::CtrlArrow(Arrow::Right),
     [27, 91, 49, 59, 53, 65] => Key::CtrlArrow(Arrow::Up),
+    [27, 91, 49, 59, 53, 80] => Key::CtrlF(FKey::F1),
+    [27, 91, 49, 59, 53, 81] => Key::CtrlF(FKey::F2),
+    [27, 91, 49, 59, 53, 82] => Key::CtrlF(FKey::F3),
+    [27, 91, 49, 59, 53, 83] => Key::CtrlF(FKey::F4),
+    [27, 91, 49, 53, 59, 53, 126] => Key::CtrlF(FKey::F5),
+    [27, 91, 49, 55, 59, 53, 126] => Key::CtrlF(FKey::F6),
+    [27, 91, 49, 56, 59, 53, 126] => Key::CtrlF(FKey::F7),
+    [27, 91, 49, 57, 59, 53, 126] => Key::CtrlF(FKey::F8),
+    [27, 91, 50, 48, 59, 53, 126] => Key::CtrlF(FKey::F9),
+    [27, 91, 50, 49, 59, 53, 126] => Key::CtrlF(FKey::F10),
+    [27, 91, 50, 51, 59, 53, 126] => Key::CtrlF(FKey::F11),
+    [27, 91, 50, 52, 59, 53, 126] => Key::CtrlF(FKey::F12),
     [27, 91, 51, 126] => Key::Del,
     [27, 91, 54, 126] => Key::PageDown,
     [27, 91, 53, 126] => Key::PageUp,
-    [27, 91, 49, 59, 50, 65] => Key::ShiftUp,
-    [27, 91, 49, 59, 50, 66] => Key::ShiftDown,
+    [27, 91, 49, 59, 50, 65] => Key::ShiftArrow(Arrow::Up),
+    [27, 91, 49, 59, 50, 66] => Key::ShiftArrow(Arrow::Down),
+    [27, 91, 49, 59, 50, 68] => Key::ShiftArrow(Arrow::Left),
+    [27, 91, 49, 59, 50, 67] => Key::ShiftArrow(Arrow::Right),
+    [27, 91, 49, 59, 50, 80] => Key::ShiftF(FKey::F1),
+    [27, 91, 49, 59, 50, 81] => Key::ShiftF(FKey::F2),
+    [27, 91, 49, 59, 50, 82] => Key::ShiftF(FKey::F3),
+    [27, 91, 49, 59, 50, 83] => Key::ShiftF(FKey::F4),
+    [27, 91, 49, 53, 59, 50, 126] => Key::ShiftF(FKey::F5),
+    [27, 91, 49, 55, 59, 50, 126] => Key::ShiftF(FKey::F6),
+    [27, 91, 49, 56, 59, 50, 126] => Key::ShiftF(FKey::F7),
+    [27, 91, 49, 57, 59, 50, 126] => Key::ShiftF(FKey::F8),
+    [27, 91, 50, 48, 59, 50, 126] => Key::ShiftF(FKey::F9),
+    [27, 91, 50, 49, 59, 50, 126] => Key::ShiftF(FKey::F10),
+    [27, 91, 50, 51, 59, 50, 126] => Key::ShiftF(FKey::F11),
+    [27, 91, 50, 52, 59, 50, 126] => Key::ShiftF(FKey::F12),
     [27, 91, 72] => Key::Home,
     [27, 91, 70] => Key::End,
     [27, 79, 72] => Key::Home,
     [27, 79, 70] => Key::End,
     [27, 91, 52, 126] => Key::End,
+    [27, 79, 80] => Key::FKey(FKey::F1),
+    [27, 79, 81] => Key::FKey(FKey::F2),
+    [27, 79, 82] => Key::FKey(FKey::F3),
+    [27, 79, 83] => Key::FKey(FKey::F4),
+    [27, 91, 49, 53, 126] => Key::FKey(FKey::F5),
+    [27, 91, 49, 55, 126] => Key::FKey(FKey::F6),
+    [27, 91, 49, 56, 126] => Key::FKey(FKey::F7),
+    [27, 91, 49, 57, 126] => Key::FKey(FKey::F8),
+    [27, 91, 50, 48, 126] => Key::FKey(FKey::F9),
+    [27, 91, 50, 49, 126] => Key::FKey(FKey::F10),
+    [27, 91, 50, 51, 126] => Key::FKey(FKey::F11),
+    [27, 91, 50, 52, 126] => Key::FKey(FKey::F12),
     [9] => Key::Tab,
     [8] => Key::Backspace,
     [127] => Key::Backspace,
@@ -134,6 +203,9 @@ byte_seq_parser! {
     [24] => Key::Ctrl('x'),
     [25] => Key::Ctrl('y'),
     [26] => Key::Ctrl('z'),
+    [28] => Key::Ctrl('\\'),
+    [29] => Key::Ctrl(']'),
+    [31] => Key::Ctrl('/'),
 }
 
 // static XTERM_FOCUS_GAINED: [u8; 3] = [27, 91, 73];


Test patch: