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
work
  • Loading branch information
imsnif committed May 22, 2024
commit 4f73e387f42ba13b8134d43577449eb5d5def2d6
489 changes: 244 additions & 245 deletions default-plugins/fixture-plugin-for-tests/src/main.rs

Large diffs are not rendered by default.

395 changes: 220 additions & 175 deletions default-plugins/session-manager/src/main.rs

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions default-plugins/session-manager/src/new_session_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,24 @@ impl NewSessionInfo {
},
}
}
pub fn handle_key(&mut self, key: Key) {
match key {
Key::Backspace => {
pub fn handle_key(&mut self, key: KeyWithModifier) {
match key.bare_key {
BareKey::Backspace if key.has_no_modifiers() => {
self.handle_backspace();
},
Key::Ctrl('c') | Key::Esc => {
BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.handle_break();
}
BareKey::Esc if key.has_no_modifiers() => {
self.handle_break();
},
Key::Char(character) => {
BareKey::Char(character) if key.has_no_modifiers() => {
self.add_char(character);
},
Key::Up => {
BareKey::Up if key.has_no_modifiers() => {
self.move_selection_up();
},
Key::Down => {
BareKey::Down if key.has_no_modifiers() => {
self.move_selection_down();
},
_ => {},
Expand Down
22 changes: 11 additions & 11 deletions default-plugins/strider/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,44 +64,44 @@ impl ZellijPlugin for State {
should_render = true;
},
Event::Key(key) => match key.bare_key {
BareKey::Char(character) if key.key_modifiers.is_empty() => {
BareKey::Char(character) if key.has_no_modifiers() => {
self.update_search_term(character);
should_render = true;
},
BareKey::Backspace if key.key_modifiers.is_empty() => {
BareKey::Backspace if key.has_no_modifiers() => {
self.handle_backspace();
should_render = true;
},
BareKey::Esc if key.key_modifiers.is_empty() => {
BareKey::Esc if key.has_no_modifiers() => {
self.clear_search_term_or_descend();
should_render = true;
},
BareKey::Char('c') if key.key_modifiers.contains(&KeyModifier::Ctrl) => {
BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.clear_search_term_or_descend();
}
BareKey::Up if key.key_modifiers.is_empty() => {
BareKey::Up if key.has_no_modifiers() => {
self.move_selection_up();
should_render = true;
},
BareKey::Down if key.key_modifiers.is_empty() => {
BareKey::Down if key.has_no_modifiers() => {
self.move_selection_down();
should_render = true;
},
BareKey::Enter if key.key_modifiers.is_empty() && self.handling_filepick_request_from.is_some() => {
BareKey::Enter if key.has_no_modifiers() && self.handling_filepick_request_from.is_some() => {
self.send_filepick_response();
},
BareKey::Enter if key.key_modifiers.is_empty() => {
BareKey::Enter if key.has_no_modifiers() => {
self.open_selected_path();
},
BareKey::Right | BareKey::Tab if key.key_modifiers.is_empty() => {
BareKey::Right | BareKey::Tab if key.has_no_modifiers() => {
self.traverse_dir();
should_render = true;
},
BareKey::Left if key.key_modifiers.is_empty() => {
BareKey::Left if key.has_no_modifiers() => {
self.descend_to_previous_path();
should_render = true;
},
BareKey::Char('e') if key.key_modifiers.contains(&KeyModifier::Ctrl)=> {
BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl])=> {
should_render = true;
self.toggle_hidden_files();
refresh_directory(&self.file_list_view.path);
Expand Down
1 change: 0 additions & 1 deletion zellij-server/src/panes/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2443,7 +2443,6 @@ impl Perform for Grid {
}

fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], _ignore: bool, c: char) {
log::info!("csi_dispatch: params: {:?}, intermediates: {:?}, char: {:?}", params, intermediates, c);
let mut params_iter = params.iter();
let mut next_param_or = |default: u16| {
params_iter
Expand Down
54 changes: 40 additions & 14 deletions zellij-server/src/panes/plugin_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::collections::{BTreeSet, HashMap};
use std::time::Instant;

use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId};
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId, terminal_pane::{BRACKETED_PASTE_BEGIN, BRACKETED_PASTE_END}};
use crate::plugins::PluginInstruction;
use crate::pty::VteBytes;
use crate::tab::{AdjustedInput, Pane};
Expand All @@ -13,7 +13,7 @@ use crate::ui::{
use crate::ClientId;
use std::cell::RefCell;
use std::rc::Rc;
use zellij_utils::data::{PermissionStatus, PermissionType, PluginPermission, KeyWithModifier};
use zellij_utils::data::{PermissionStatus, PermissionType, PluginPermission, KeyWithModifier, BareKey};
use zellij_utils::pane_size::{Offset, SizeInPixels};
use zellij_utils::position::Position;
use zellij_utils::{
Expand Down Expand Up @@ -235,19 +235,45 @@ impl Pane for PluginPane {
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();
match raw_input_bytes.as_slice() {
// Y or y
&[89] | &[121] => Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Granted,
)),
// N or n
&[78] | &[110] => Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Denied,
)),
_ => None,
if let Some(key_with_modifier) = key_with_modifier {
match key_with_modifier.bare_key {
BareKey::Char('y') if key_with_modifier.has_no_modifiers() => {
Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Granted,
))
},
BareKey::Char('n') if key_with_modifier.has_no_modifiers() => {
Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Denied,
))
}
_ => {
None
}

}
} else {
match raw_input_bytes.as_slice() {
// Y or y
&[89] | &[121] => Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Granted,
)),
// N or n
&[78] | &[110] => Some(AdjustedInput::PermissionRequestResult(
permissions,
PermissionStatus::Denied,
)),
_ => None,
}
}
} else if let Some(key_with_modifier) = key_with_modifier {
Some(AdjustedInput::WriteKeyToPlugin(key_with_modifier.clone()))
} else if raw_input_bytes.as_slice() == BRACKETED_PASTE_BEGIN || raw_input_bytes.as_slice() == BRACKETED_PASTE_END {
// plugins do not need bracketed paste
None
} else {
Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
}
Expand Down
4 changes: 2 additions & 2 deletions zellij-server/src/panes/terminal_pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const UP_ARROW: &[u8] = &[27, 91, 65];
const DOWN_ARROW: &[u8] = &[27, 91, 66];
const HOME_KEY: &[u8] = &[27, 91, 72];
const END_KEY: &[u8] = &[27, 91, 70];
const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126];
const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126];
pub const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126];
pub const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126];
const ENTER_NEWLINE: &[u8] = &[10];
const ESC: &[u8] = &[27];
const ENTER_CARRIAGE_RETURN: &[u8] = &[13];
Expand Down
9 changes: 7 additions & 2 deletions zellij-server/src/tab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ pub enum AdjustedInput {
PermissionRequestResult(Vec<PermissionType>, PermissionStatus),
CloseThisPane,
DropToShellInThisPane { working_dir: Option<PathBuf> },
WriteKeyToPlugin(KeyWithModifier),
}
pub fn get_next_terminal_position(
tiled_panes: &TiledPanes,
Expand Down Expand Up @@ -1765,13 +1766,17 @@ impl Tab {
}
},
PaneId::Plugin(pid) => match active_terminal.adjust_input_to_terminal(key_with_modifier, raw_input_bytes, raw_input_bytes_are_kitty) {
Some(AdjustedInput::WriteKeyToPlugin(key_with_modifier)) => {
self.senders
.send_to_plugin(PluginInstruction::Update(vec![(Some(pid), client_id, Event::Key(key_with_modifier))]))
.with_context(err_context)?;
}
Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => {
let mut plugin_updates = vec![];
// TODO: instead of doing another parse here, let's just send key_with_modifier
// directly in the event
for key in parse_keys(&adjusted_input) {
plugin_updates.push((Some(pid), client_id, Event::Key(key)));
}
log::info!("plugin_updates: {:?}", plugin_updates);
self.senders
.send_to_plugin(PluginInstruction::Update(plugin_updates))
.with_context(err_context)?;
Expand Down
Binary file modified zellij-utils/assets/plugins/compact-bar.wasm
Binary file not shown.
Binary file modified zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm
Binary file not shown.
Binary file modified zellij-utils/assets/plugins/session-manager.wasm
Binary file not shown.
Binary file modified zellij-utils/assets/plugins/status-bar.wasm
Binary file not shown.
Binary file modified zellij-utils/assets/plugins/strider.wasm
Binary file not shown.
Binary file modified zellij-utils/assets/plugins/tab-bar.wasm
Binary file not shown.
12 changes: 11 additions & 1 deletion zellij-utils/src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,9 +653,19 @@ impl KeyWithModifier {
newline_mode: false,
modify_other_keys: None
};
log::info!("self: {:?}, to_termwiz_keycode: {:?}", self, self.to_termwiz_keycode());
self.to_termwiz_keycode().encode(modifiers, key_code_encode_modes, true).ok()
}
pub fn has_no_modifiers(&self) -> bool {
self.key_modifiers.is_empty()
}
pub fn has_modifiers(&self, modifiers: &[KeyModifier]) -> bool {
for modifier in modifiers {
if !self.key_modifiers.contains(modifier) {
return false;
}
}
true
}
}

#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
Expand Down