summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBradford Hovinen <hovinen@gmail.com>2024-03-25 15:04:38 +0100
committerGitHub <noreply@github.com>2024-03-25 15:04:38 +0100
commit312817fcf6eb86e394ee72986bd00bede76d70f4 (patch)
tree38aa85346a611dc2c9151a27ca60ce64ee295d72
parent223730f04a008237fc0b78b788cfc62dd896d51c (diff)
fix(config): add support for Ctrl and Alt modifiers on function keys in keybindings (#3179)
Previously, it was not possible to define keybindings with a modifier and a function key. The `Key` enum only supported combinging the Ctrl and Alt modifiers with letters. This is somewhat limiting: I would like to make the Zellij keybindings more "distant" than those used in the programs I use from within Zelilj, so that Zellij does not intefere with those programs. Thus I would like to move some of the keybindings from Ctrl+<character> to Ctrl+<function key>. This change adds: * support for function keys with the Ctrl and Alt modifiers in the `Key` enum, * support for parsing such keybindings from the configuration file, * support for such keybindings in the protobuf which communicates the keybindings to plugins, and * support for these keybindings in the plugin API. This is tested by modifying one of the e2e tests to include an example of such keybindings. This verifies that the configuration is correctly parsed, communicated with the plugin, and rendered.
-rw-r--r--default-plugins/status-bar/src/first_line.rs2
-rw-r--r--default-plugins/status-bar/src/main.rs6
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap2
-rw-r--r--src/tests/fixtures/configs/changed_keys.kdl4
-rw-r--r--zellij-utils/src/data.rs82
-rw-r--r--zellij-utils/src/input/mod.rs10
-rw-r--r--zellij-utils/src/plugin_api/key.rs86
7 files changed, 120 insertions, 72 deletions
diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs
index 23a6d5869..4ac090646 100644
--- a/default-plugins/status-bar/src/first_line.rs
+++ b/default-plugins/status-bar/src/first_line.rs
@@ -62,6 +62,8 @@ impl KeyShortcut {
} else {
match key {
Key::F(c) => format!("{}", c),
+ Key::CtrlF(n) => format!("F{}", n),
+ Key::AltF(n) => format!("F{}", n),
Key::Ctrl(c) => format!("{}", c),
Key::Char(_) => format!("{}", key),
Key::Alt(c) => format!("{}", c),
diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs
index 2910d6786..1d85c7f69 100644
--- a/default-plugins/status-bar/src/main.rs
+++ b/default-plugins/status-bar/src/main.rs
@@ -333,8 +333,8 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option<String> {
let mut new_modifier;
for key in keyvec.iter() {
match key {
- Key::Ctrl(_) => new_modifier = "Ctrl",
- Key::Alt(_) => new_modifier = "Alt",
+ Key::Ctrl(_) | Key::CtrlF(_) => new_modifier = "Ctrl",
+ Key::Alt(_) | Key::AltF(_) => new_modifier = "Alt",
_ => return None,
}
if modifier.is_empty() {
@@ -468,7 +468,9 @@ pub fn style_key_with_modifier(
} else {
match key {
Key::Ctrl(c) => format!("{}", Key::Char(*c)),
+ Key::CtrlF(n) => format!("{}", Key::F(*n)),
Key::Alt(c) => format!("{}", c),
+ Key::AltF(n) => format!("{}", Key::F(*n)),
_ => format!("{}", key),
}
}
diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap
index 2f983243f..974a63bd8 100644
--- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap
+++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap
@@ -25,5 +25,5 @@ expression: last_snapshot
│ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
- <F1> LOCK  <F2> PANE  <F3> TAB  <F4> RESIZE  <F5> MOVE  <F6> SEARCH  <F7> SESSION  <F8> QUIT 
+ <F1> LOCK  <F2> PANE  <F3> TAB  <F4> RESIZE  <F5> MOVE  <F6> SEARCH  <Alt+F7> SESSION  <Ctrl+F8> QUIT 
Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size.
diff --git a/src/tests/fixtures/configs/changed_keys.kdl b/src/tests/fixtures/configs/changed_keys.kdl
index 6123b1d5b..c8bed1be2 100644
--- a/src/tests/fixtures/configs/changed_keys.kdl
+++ b/src/tests/fixtures/configs/changed_keys.kdl
@@ -6,7 +6,7 @@ keybinds clear-defaults=true {
bind "F4" { SwitchToMode "Resize"; }
bind "F5" { SwitchToMode "Move"; }
bind "F6" { SwitchToMode "Scroll"; }
- bind "F7" { SwitchToMode "Session"; }
- bind "F8" { Quit; }
+ bind "Alt F7" { SwitchToMode "Session"; }
+ bind "Ctrl F8" { Quit; }
}
}
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 998392ff7..204c57e43 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -62,6 +62,8 @@ pub enum Key {
BackTab,
Null,
Esc,
+ AltF(u8),
+ CtrlF(u8),
}
impl FromStr for Key {
@@ -78,14 +80,7 @@ impl FromStr for Key {
}
match (modifier, main_key) {
(Some("Ctrl"), Some(main_key)) => {
- let mut key_chars = main_key.chars();
- let key_count = main_key.chars().count();
- if key_count == 1 {
- let key_char = key_chars.next().unwrap();
- Ok(Key::Ctrl(key_char))
- } else {
- Err(format!("Failed to parse key: {}", key_str).into())
- }
+ parse_main_key(main_key, key_str, Key::Ctrl, Key::CtrlF)
},
(Some("Alt"), Some(main_key)) => {
match main_key {
@@ -96,16 +91,12 @@ impl FromStr for Key {
"Right" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Right))),
"Up" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Up))),
"Down" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Down))),
- _ => {
- let mut key_chars = main_key.chars();
- let key_count = main_key.chars().count();
- if key_count == 1 {
- let key_char = key_chars.next().unwrap();
- Ok(Key::Alt(CharOrArrow::Char(key_char)))
- } else {
- Err(format!("Failed to parse key: {}", key_str).into())
- }
- },
+ _ => parse_main_key(
+ main_key,
+ key_str,
+ |c| Key::Alt(CharOrArrow::Char(c)),
+ Key::AltF,
+ ),
}
},
(None, Some(main_key)) => match main_key {
@@ -124,35 +115,42 @@ impl FromStr for Key {
"Space" => Ok(Key::Char(' ')),
"Enter" => Ok(Key::Char('\n')),
"Esc" => Ok(Key::Esc),
- _ => {
- let mut key_chars = main_key.chars();
- let key_count = main_key.chars().count();
- if key_count == 1 {
- let key_char = key_chars.next().unwrap();
- Ok(Key::Char(key_char))
- } else if key_count > 1 {
- if let Some(first_char) = key_chars.next() {
- if first_char == 'F' {
- let f_index: String = key_chars.collect();
- let f_index: u8 = f_index
- .parse()
- .map_err(|e| format!("Failed to parse F index: {}", e))?;
- if f_index >= 1 && f_index <= 12 {
- return Ok(Key::F(f_index));
- }
- }
- }
- Err(format!("Failed to parse key: {}", key_str).into())
- } else {
- Err(format!("Failed to parse key: {}", key_str).into())
- }
- },
+ _ => parse_main_key(main_key, key_str, Key::Char, Key::F),
},
_ => Err(format!("Failed to parse key: {}", key_str).into()),
}
}
}
+fn parse_main_key(
+ main_key: &str,
+ key_str: &str,
+ to_char_key: impl FnOnce(char) -> Key,
+ to_fn_key: impl FnOnce(u8) -> Key,
+) -> Result<Key, Box<dyn std::error::Error>> {
+ let mut key_chars = main_key.chars();
+ let key_count = main_key.chars().count();
+ if key_count == 1 {
+ let key_char = key_chars.next().unwrap();
+ Ok(to_char_key(key_char))
+ } else if key_count > 1 {
+ if let Some(first_char) = key_chars.next() {
+ if first_char == 'F' {
+ let f_index: String = key_chars.collect();
+ let f_index: u8 = f_index
+ .parse()
+ .map_err(|e| format!("Failed to parse F index: {}", e))?;
+ if f_index >= 1 && f_index <= 12 {
+ return Ok(to_fn_key(f_index));
+ }
+ }
+ }
+ Err(format!("Failed to parse key: {}", key_str).into())
+ } else {
+ Err(format!("Failed to parse key: {}", key_str).into())
+ }
+}
+
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -177,6 +175,8 @@ impl fmt::Display for Key {
},
Key::Alt(c) => write!(f, "Alt+{}", c),
Key::Ctrl(c) => write!(f, "Ctrl+{}", Key::Char(*c)),
+ Key::AltF(n) => write!(f, "Alt+F{}", n),
+ Key::CtrlF(n) => write!(f, "Ctrl+F{}", n),
Key::Null => write!(f, "NULL"),
Key::Esc => write!(f, "ESC"),
}
diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs
index 1c95f348b..1d30841f3 100644
--- a/zellij-utils/src/input/mod.rs
+++ b/zellij-utils/src/input/mod.rs
@@ -113,7 +113,15 @@ mod not_wasm {
KeyCode::Tab => Key::BackTab, // TODO: ???
KeyCode::Delete => Key::Delete,
KeyCode::Insert => Key::Insert,
- KeyCode::Function(n) => Key::F(n),
+ KeyCode::Function(n) => {
+ if modifiers.contains(Modifiers::ALT) {
+ Key::AltF(n)
+ } else if modifiers.contains(Modifiers::CTRL) {
+ Key::CtrlF(n)
+ } else {
+ Key::F(n)
+ }
+ },
KeyCode::Escape => Key::Esc,
KeyCode::Enter => Key::Char('\n'),
_ => Key::Esc, // there are other keys we can implement here, but we might need additional terminal support to implement them, not just exhausting this enum
diff --git a/zellij-utils/src/plugin_api/key.rs b/zellij-utils/src/plugin_api/key.rs
index 7cef4ba76..c5ce10870 100644
--- a/zellij-utils/src/plugin_api/key.rs
+++ b/zellij-utils/src/plugin_api/key.rs
@@ -12,12 +12,21 @@ impl TryFrom<ProtobufKey> for Key {
let key_modifier = parse_optional_modifier(&protobuf_key);
match key_modifier {
Some(KeyModifier::Ctrl) => {
- let character = char_from_main_key(protobuf_key.main_key)?;
- Ok(Key::Ctrl(character))
+ if let Ok(character) = char_from_main_key(protobuf_key.main_key.clone()) {
+ Ok(Key::Ctrl(character))
+ } else {
+ let index = fn_index_from_main_key(protobuf_key.main_key)?;
+ Ok(Key::CtrlF(index))
+ }
},
Some(KeyModifier::Alt) => {
- let char_or_arrow = CharOrArrow::from_main_key(protobuf_key.main_key)?;
- Ok(Key::Alt(char_or_arrow))
+ if let Ok(char_or_arrow) = CharOrArrow::from_main_key(protobuf_key.main_key.clone())
+ {
+ Ok(Key::Alt(char_or_arrow))
+ } else {
+ let index = fn_index_from_main_key(protobuf_key.main_key)?;
+ Ok(Key::AltF(index))
+ }
},
None => match protobuf_key.main_key.as_ref().ok_or("invalid key")? {
MainKey::Char(_key_index) => {
@@ -81,27 +90,18 @@ impl TryFrom<Key> for ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Insert as i32)),
}),
- Key::F(index) => {
- let main_key = match index {
- 1 => Some(MainKey::Key(NamedKey::F1 as i32)),
- 2 => Some(MainKey::Key(NamedKey::F2 as i32)),
- 3 => Some(MainKey::Key(NamedKey::F3 as i32)),
- 4 => Some(MainKey::Key(NamedKey::F4 as i32)),
- 5 => Some(MainKey::Key(NamedKey::F5 as i32)),
- 6 => Some(MainKey::Key(NamedKey::F6 as i32)),
- 7 => Some(MainKey::Key(NamedKey::F7 as i32)),
- 8 => Some(MainKey::Key(NamedKey::F8 as i32)),
- 9 => Some(MainKey::Key(NamedKey::F9 as i32)),
- 10 => Some(MainKey::Key(NamedKey::F10 as i32)),
- 11 => Some(MainKey::Key(NamedKey::F11 as i32)),
- 12 => Some(MainKey::Key(NamedKey::F12 as i32)),
- _ => return Err("Invalid key"),
- };
- Ok(ProtobufKey {
- modifier: None,
- main_key,
- })
- },
+ Key::F(index) => Ok(ProtobufKey {
+ modifier: None,
+ main_key: Some(fn_index_to_main_key(index)?),
+ }),
+ Key::CtrlF(index) => Ok(ProtobufKey {
+ modifier: Some(KeyModifier::Ctrl as i32),
+ main_key: Some(fn_index_to_main_key(index)?),
+ }),
+ Key::AltF(index) => Ok(ProtobufKey {
+ modifier: Some(KeyModifier::Alt as i32),
+ main_key: Some(fn_index_to_main_key(index)?),
+ }),
Key::Char(character) => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Char((character as u8) as i32)),
@@ -147,6 +147,24 @@ impl TryFrom<Key> for ProtobufKey {
}
}
+fn fn_index_to_main_key(index: u8) -> Result<MainKey, &'static str> {
+ match index {
+ 1 => Ok(MainKey::Key(NamedKey::F1 as i32)),
+ 2 => Ok(MainKey::Key(NamedKey::F2 as i32)),
+ 3 => Ok(MainKey::Key(NamedKey::F3 as i32)),
+ 4 => Ok(MainKey::Key(NamedKey::F4 as i32)),
+ 5 => Ok(MainKey::Key(NamedKey::F5 as i32)),
+ 6 => Ok(MainKey::Key(NamedKey::F6 as i32)),
+ 7 => Ok(MainKey::Key(NamedKey::F7 as i32)),
+ 8 => Ok(MainKey::Key(NamedKey::F8 as i32)),
+ 9 => Ok(MainKey::Key(NamedKey::F9 as i32)),
+ 10 => Ok(MainKey::Key(NamedKey::F10 as i32)),
+ 11 => Ok(MainKey::Key(NamedKey::F11 as i32)),
+ 12 => Ok(MainKey::Key(NamedKey::F12 as i32)),
+ _ => Err("Invalid key"),
+ }
+}
+
impl CharOrArrow {
pub fn from_main_key(
main_key: std::option::Option<MainKey>,
@@ -191,6 +209,24 @@ fn char_from_main_key(main_key: Option<MainKey>) -> Result<char, &'static str> {
}
}
+fn fn_index_from_main_key(main_key: Option<MainKey>) -> Result<u8, &'static str> {
+ match main_key {
+ Some(MainKey::Key(n)) if n == NamedKey::F1 as i32 => Ok(1),
+ Some(MainKey::Key(n)) if n == NamedKey::F2 as i32 => Ok(2),
+ Some(MainKey::Key(n)) if n == NamedKey::F3 as i32 => Ok(3),
+ Some(MainKey::Key(n)) if n == NamedKey::F4 as i32 => Ok(4),
+ Some(MainKey::Key(n)) if n == NamedKey::F5 as i32 => Ok(5),
+ Some(MainKey::Key(n)) if n == NamedKey::F6 as i32 => Ok(6),
+ Some(MainKey::Key(n)) if n == NamedKey::F7 as i32 => Ok(7),
+ Some(MainKey::Key(n)) if n == NamedKey::F8 as i32 => Ok(8),
+ Some(MainKey::Key(n)) if n == NamedKey::F9 as i32 => Ok(9),
+ Some(MainKey::Key(n)) if n == NamedKey::F10 as i32 => Ok(10),
+ Some(MainKey::Key(n)) if n == NamedKey::F11 as i32 => Ok(11),
+ Some(MainKey::Key(n)) if n == NamedKey::F12 as i32 => Ok(12),
+ _ => Err("Unsupported key"),
+ }
+}
+
fn named_key_to_key(named_key: NamedKey) -> Key {
match named_key {
NamedKey::PageDown => Key::PageDown,