Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(keybindings): support multiple modifiers (eg. Ctrl+Alt) and the kitty keyboard protocol #3383

Merged
merged 15 commits into from
May 27, 2024
Prev Previous commit
Next Next commit
various cleanups
  • Loading branch information
imsnif committed May 27, 2024
commit f088a9a72921bc8fd2318b45415769f41309808c
16 changes: 0 additions & 16 deletions default-plugins/status-bar/src/first_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,6 @@ impl KeyShortcut {
None => return String::from("?"),
};
format!("{}", key)
// if with_prefix {
// format!("{}", key)
// } else {
// format!("{}", key.bare_key)
// // 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),
// // _ => String::from("??"),
// // }
// }
}
}

Expand Down Expand Up @@ -281,7 +267,6 @@ fn key_indicators(
) -> LinePart {
// Print full-width hints
let (shared_modifiers, mut line_part) = superkey(palette, separator, mode_info);
// let shared_super = line_part.len > 0;
for key in keys {
let line_empty = line_part.len == 0;
let key = long_mode_shortcut(key, palette, separator, &shared_modifiers, line_empty);
Expand All @@ -300,7 +285,6 @@ fn key_indicators(
line_part.part = format!("{}{}", line_part.part, key.part);
line_part.len += key.len;
}
eprintln!("line_part: {}", line_part.part);
if line_part.len < max_len {
return line_part;
}
Expand Down
3 changes: 0 additions & 3 deletions zellij-client/src/input_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,7 @@ impl InputHandler {
}
},
Ok((InputInstruction::KeyWithModifierEvent(key_with_modifier, raw_bytes), _error_context)) => {
// self.handle_key_with_modifier(&key_with_modifier, raw_bytes);
log::info!("handling key with modifier: {:?}", key_with_modifier);
self.handle_key(&key_with_modifier, raw_bytes, true);

}
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
self.mode = input_mode;
Expand Down
6 changes: 3 additions & 3 deletions zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,9 @@ pub struct Grid {
debug: bool,
arrow_fonts: bool,
styled_underlines: bool,
pub supports_kitty_keyboard_protocol: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
pub supports_kitty_keyboard_protocol: bool, // has the app requested kitty keyboard support?
explicitly_disable_kitty_keyboard_protocol: bool, // has kitty keyboard support been explicitly
// disabled by user config?
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -508,7 +509,6 @@ impl Grid {
arrow_fonts,
styled_underlines,
lock_renders: false,
// supports_kitty_keyboard_protocol: true,
supports_kitty_keyboard_protocol: false,
explicitly_disable_kitty_keyboard_protocol,
}
Expand Down
6 changes: 5 additions & 1 deletion zellij-server/src/panes/plugin_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,11 @@ impl Pane for PluginPane {
fn cursor_coordinates(&self) -> Option<(usize, usize)> {
None
}
fn adjust_input_to_terminal(&mut self, key_with_modifier: &Option<KeyWithModifier>, raw_input_bytes: Vec<u8>, raw_input_bytes_are_kitty: bool) -> Option<AdjustedInput> {
fn adjust_input_to_terminal(&mut self,
key_with_modifier: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
_raw_input_bytes_are_kitty: bool
) -> Option<AdjustedInput> {
if let Some(requesting_permissions) = &self.requesting_permissions {
let permissions = requesting_permissions.permissions.clone();
if let Some(key_with_modifier) = key_with_modifier {
Expand Down
2 changes: 1 addition & 1 deletion zellij-server/src/tab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub trait Pane {
fn handle_pty_bytes(&mut self, _bytes: VteBytes) {}
fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {}
fn cursor_coordinates(&self) -> Option<(usize, usize)>;
fn adjust_input_to_terminal(&mut self, key_with_modifier: &Option<KeyWithModifier>, raw_input_bytes: Vec<u8>, raw_input_bytes_are_kitty: bool) -> Option<AdjustedInput> {
fn adjust_input_to_terminal(&mut self, _key_with_modifier: &Option<KeyWithModifier>, _raw_input_bytes: Vec<u8>, _raw_input_bytes_are_kitty: bool) -> Option<AdjustedInput> {
None
}
fn position_and_size(&self) -> PaneGeom;
Expand Down
187 changes: 0 additions & 187 deletions zellij-utils/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,94 +43,6 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
(colors.green, colors.black)
}

// TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when
// screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode
// representations instead.
// NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin
// displays!
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
pub enum Key {
PageDown,
PageUp,
Left,
Down,
Up,
Right,
Home,
End,
Backspace,
Delete,
Insert,
F(u8),
Char(char),
Alt(CharOrArrow),
Ctrl(char),
BackTab,
Null,
Esc,
AltF(u8),
CtrlF(u8),
}

impl FromStr for Key {
type Err = Box<dyn std::error::Error>;
fn from_str(key_str: &str) -> Result<Self, Self::Err> {
let mut modifier: Option<&str> = None;
let mut main_key: Option<&str> = None;
for (index, part) in key_str.split_ascii_whitespace().enumerate() {
if index == 0 && (part == "Ctrl" || part == "Alt") {
modifier = Some(part);
} else if main_key.is_none() {
main_key = Some(part)
}
}
match (modifier, main_key) {
(Some("Ctrl"), Some(main_key)) if main_key == "@" || main_key == "Space" => {
Ok(Key::Char('\x00'))
},
(Some("Ctrl"), Some(main_key)) => {
parse_main_key(main_key, key_str, Key::Ctrl, Key::CtrlF)
},
(Some("Alt"), Some(main_key)) => {
match main_key {
// why crate::data::Direction and not just Direction?
// Because it's a different type that we export in this wasm mandated soup - we
// don't like it either! This will be solved as we chip away at our tech-debt
"Left" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Left))),
"Right" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Right))),
"Up" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Up))),
"Down" => Ok(Key::Alt(CharOrArrow::Direction(Direction::Down))),
_ => parse_main_key(
main_key,
key_str,
|c| Key::Alt(CharOrArrow::Char(c)),
Key::AltF,
),
}
},
(None, Some(main_key)) => match main_key {
"Backspace" => Ok(Key::Backspace),
"Left" => Ok(Key::Left),
"Right" => Ok(Key::Right),
"Up" => Ok(Key::Up),
"Down" => Ok(Key::Down),
"Home" => Ok(Key::Home),
"End" => Ok(Key::End),
"PageUp" => Ok(Key::PageUp),
"PageDown" => Ok(Key::PageDown),
"Tab" => Ok(Key::BackTab),
"Delete" => Ok(Key::Delete),
"Insert" => Ok(Key::Insert),
"Space" => Ok(Key::Char(' ')),
"Enter" => Ok(Key::Char('\n')),
"Esc" => Ok(Key::Esc),
_ => parse_main_key(main_key, key_str, Key::Char, Key::F),
},
_ => Err(format!("Failed to parse key: {}", key_str).into()),
}
}
}

impl FromStr for KeyWithModifier {
type Err = Box<dyn std::error::Error>;
fn from_str(key_str: &str) -> Result<Self, Self::Err> {
Expand All @@ -147,84 +59,6 @@ impl FromStr for KeyWithModifier {
}
}

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 {
Key::Backspace => write!(f, "BACKSPACE"),
Key::Left => write!(f, "{}", Direction::Left),
Key::Right => write!(f, "{}", Direction::Right),
Key::Up => write!(f, "{}", Direction::Up),
Key::Down => write!(f, "{}", Direction::Down),
Key::Home => write!(f, "HOME"),
Key::End => write!(f, "END"),
Key::PageUp => write!(f, "PgUp"),
Key::PageDown => write!(f, "PgDn"),
Key::BackTab => write!(f, "TAB"),
Key::Delete => write!(f, "DEL"),
Key::Insert => write!(f, "INS"),
Key::F(n) => write!(f, "F{}", n),
Key::Char(c) => match c {
'\n' => write!(f, "ENTER"),
'\t' => write!(f, "TAB"),
' ' => write!(f, "SPACE"),
'\x00' => write!(f, "Ctrl+SPACE"),
_ => write!(f, "{}", c),
},
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"),
}
}
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(untagged)]
pub enum CharOrArrow {
Char(char),
Direction(Direction),
}

impl fmt::Display for CharOrArrow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CharOrArrow::Char(c) => write!(f, "{}", Key::Char(*c)),
CharOrArrow::Direction(d) => write!(f, "{}", d),
}
}
}

#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub struct KeyWithModifier {
pub bare_key: BareKey,
Expand Down Expand Up @@ -618,29 +452,8 @@ impl KeyWithModifier {
pub fn is_key_with_super_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Super)
}
fn serialize_modifiers_non_kitty(&self) -> Option<&str> {
if self.key_modifiers.contains(&KeyModifier::Ctrl) && self.key_modifiers.contains(&KeyModifier::Alt) && self.key_modifiers.contains(&KeyModifier::Shift) {
Some("8")
} else if self.key_modifiers.contains(&KeyModifier::Ctrl) && self.key_modifiers.contains(&KeyModifier::Alt) {
Some("7")
} else if self.key_modifiers.contains(&KeyModifier::Ctrl) && self.key_modifiers.contains(&KeyModifier::Shift) {
Some("6")
} else if self.key_modifiers.contains(&KeyModifier::Alt) && self.key_modifiers.contains(&KeyModifier::Shift) {
Some("4")
} else if self.key_modifiers.contains(&KeyModifier::Ctrl) {
Some("5")
} else if self.key_modifiers.contains(&KeyModifier::Alt) {
Some("3")
} else if self.key_modifiers.contains(&KeyModifier::Shift) {
Some("2")
} else {
None
}

}
#[cfg(not(target_family = "wasm"))]
pub fn to_termwiz_modifiers(&self) -> Modifiers {
// TODO: CONTINUE HERE (17/05 evening) - implement this, then back to terminal_pane.rs
let mut modifiers = Modifiers::empty();
for modifier in &self.key_modifiers {
modifiers.set(modifier.into(), true);
Expand Down
2 changes: 1 addition & 1 deletion zellij-utils/src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub use not_wasm::*;
#[cfg(not(target_family = "wasm"))]
mod not_wasm {
use crate::{
data::{CharOrArrow, Direction, InputMode, Key, KeyWithModifier, BareKey, KeyModifier, ModeInfo, PluginCapabilities},
data::{InputMode, KeyWithModifier, BareKey, KeyModifier, ModeInfo, PluginCapabilities},
envs,
ipc::ClientAttributes,
};
Expand Down
5 changes: 3 additions & 2 deletions zellij-utils/src/plugin_api/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub use super::generated_api::api::{
};
#[allow(hidden_glob_reexports)]
use crate::data::{
CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, BareKey, LayoutInfo, ModeInfo, Mouse,
CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo, ModeInfo, Mouse,
PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities, SessionInfo, Style, TabInfo,
};

Expand Down Expand Up @@ -1042,7 +1042,7 @@ fn serialize_mode_update_event() {

#[test]
fn serialize_mode_update_event_with_non_default_values() {
use crate::data::{Palette, PaletteColor, ThemeHue};
use crate::data::{Palette, PaletteColor, ThemeHue, BareKey};
use prost::Message;
let mode_update_event = Event::ModeUpdate(ModeInfo {
mode: InputMode::Locked,
Expand Down Expand Up @@ -1188,6 +1188,7 @@ fn serialize_pane_update_event() {
#[test]
fn serialize_key_event() {
use prost::Message;
use crate::data::BareKey;
let key_event = Event::Key(KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier());
let protobuf_event: ProtobufEvent = key_event.clone().try_into().unwrap();
let serialized_protobuf_event = protobuf_event.encode_to_vec();
Expand Down
Loading