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

* parse kitty keys from STDIN

* work

* work

* replace internal Key representation with the new KeyWithModifier in all the places

* work

* work

* allow disabling with config

* adjust ordering

* handle enabling/disabling properly on the client

* support multiple modifiers without kitty too

* normalize uppercase keys

* get tests to pass

* various cleanups

* style(fmt): rustfmt
This commit is contained in:
Aram Drevekenin 2024-05-27 16:15:09 +02:00 committed by GitHub
parent c72f3a712b
commit 62c37a87cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
66 changed files with 21302 additions and 11902 deletions

1
Cargo.lock generated
View file

@ -5306,6 +5306,7 @@ dependencies = [
"async-channel", "async-channel",
"async-std", "async-std",
"backtrace", "backtrace",
"bitflags 2.5.0",
"clap", "clap",
"clap_complete", "clap_complete",
"colored", "colored",

View file

@ -72,11 +72,11 @@ impl ZellijPlugin for State {
fn update(&mut self, event: Event) -> bool { fn update(&mut self, event: Event) -> bool {
match &event { match &event {
Event::Key(key) => match key { Event::Key(key) => match key.bare_key {
Key::Char('a') => { BareKey::Char('a') if key.has_no_modifiers() => {
switch_to_input_mode(&InputMode::Tab); switch_to_input_mode(&InputMode::Tab);
}, },
Key::Char('b') => { BareKey::Char('b') if key.has_no_modifiers() => {
new_tabs_with_layout( new_tabs_with_layout(
"layout { "layout {
tab { tab {
@ -90,85 +90,87 @@ impl ZellijPlugin for State {
}", }",
); );
}, },
Key::Char('c') => new_tab(), BareKey::Char('c') if key.has_no_modifiers() => new_tab(),
Key::Char('d') => go_to_next_tab(), BareKey::Char('d') if key.has_no_modifiers() => go_to_next_tab(),
Key::Char('e') => go_to_previous_tab(), BareKey::Char('e') if key.has_no_modifiers() => go_to_previous_tab(),
Key::Char('f') => { BareKey::Char('f') if key.has_no_modifiers() => {
let resize = Resize::Increase; let resize = Resize::Increase;
resize_focused_pane(resize) resize_focused_pane(resize)
}, },
Key::Char('g') => { BareKey::Char('g') if key.has_no_modifiers() => {
let resize = Resize::Increase; let resize = Resize::Increase;
let direction = Direction::Left; let direction = Direction::Left;
resize_focused_pane_with_direction(resize, direction); resize_focused_pane_with_direction(resize, direction);
}, },
Key::Char('h') => focus_next_pane(), BareKey::Char('h') if key.has_no_modifiers() => focus_next_pane(),
Key::Char('i') => focus_previous_pane(), BareKey::Char('i') if key.has_no_modifiers() => focus_previous_pane(),
Key::Char('j') => { BareKey::Char('j') if key.has_no_modifiers() => {
let direction = Direction::Left; let direction = Direction::Left;
move_focus(direction) move_focus(direction)
}, },
Key::Char('k') => { BareKey::Char('k') if key.has_no_modifiers() => {
let direction = Direction::Left; let direction = Direction::Left;
move_focus_or_tab(direction) move_focus_or_tab(direction)
}, },
Key::Char('l') => detach(), BareKey::Char('l') if key.has_no_modifiers() => detach(),
Key::Char('m') => edit_scrollback(), BareKey::Char('m') if key.has_no_modifiers() => edit_scrollback(),
Key::Char('n') => { BareKey::Char('n') if key.has_no_modifiers() => {
let bytes = vec![102, 111, 111]; let bytes = vec![102, 111, 111];
write(bytes) write(bytes)
}, },
Key::Char('o') => { BareKey::Char('o') if key.has_no_modifiers() => {
let chars = "foo"; let chars = "foo";
write_chars(chars); write_chars(chars);
}, },
Key::Char('p') => toggle_tab(), BareKey::Char('p') if key.has_no_modifiers() => toggle_tab(),
Key::Char('q') => move_pane(), BareKey::Char('q') if key.has_no_modifiers() => move_pane(),
Key::Char('r') => { BareKey::Char('r') if key.has_no_modifiers() => {
let direction = Direction::Left; let direction = Direction::Left;
move_pane_with_direction(direction) move_pane_with_direction(direction)
}, },
Key::Char('s') => clear_screen(), BareKey::Char('s') if key.has_no_modifiers() => clear_screen(),
Key::Char('t') => scroll_up(), BareKey::Char('t') if key.has_no_modifiers() => scroll_up(),
Key::Char('u') => scroll_down(), BareKey::Char('u') if key.has_no_modifiers() => scroll_down(),
Key::Char('v') => scroll_to_top(), BareKey::Char('v') if key.has_no_modifiers() => scroll_to_top(),
Key::Char('w') => scroll_to_bottom(), BareKey::Char('w') if key.has_no_modifiers() => scroll_to_bottom(),
Key::Char('x') => page_scroll_up(), BareKey::Char('x') if key.has_no_modifiers() => page_scroll_up(),
Key::Char('y') => page_scroll_down(), BareKey::Char('y') if key.has_no_modifiers() => page_scroll_down(),
Key::Char('z') => toggle_focus_fullscreen(), BareKey::Char('z') if key.has_no_modifiers() => toggle_focus_fullscreen(),
Key::Char('1') => toggle_pane_frames(), BareKey::Char('1') if key.has_no_modifiers() => toggle_pane_frames(),
Key::Char('2') => toggle_pane_embed_or_eject(), BareKey::Char('2') if key.has_no_modifiers() => toggle_pane_embed_or_eject(),
Key::Char('3') => undo_rename_pane(), BareKey::Char('3') if key.has_no_modifiers() => undo_rename_pane(),
Key::Char('4') => close_focus(), BareKey::Char('4') if key.has_no_modifiers() => close_focus(),
Key::Char('5') => toggle_active_tab_sync(), BareKey::Char('5') if key.has_no_modifiers() => toggle_active_tab_sync(),
Key::Char('6') => close_focused_tab(), BareKey::Char('6') if key.has_no_modifiers() => close_focused_tab(),
Key::Char('7') => undo_rename_tab(), BareKey::Char('7') if key.has_no_modifiers() => undo_rename_tab(),
Key::Char('8') => quit_zellij(), BareKey::Char('8') if key.has_no_modifiers() => quit_zellij(),
Key::Ctrl('a') => previous_swap_layout(), BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
Key::Ctrl('b') => next_swap_layout(), previous_swap_layout()
Key::Ctrl('c') => { },
BareKey::Char('b') if key.has_modifiers(&[KeyModifier::Ctrl]) => next_swap_layout(),
BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let tab_name = "my tab name"; let tab_name = "my tab name";
go_to_tab_name(tab_name) go_to_tab_name(tab_name)
}, },
Key::Ctrl('d') => { BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let tab_name = "my tab name"; let tab_name = "my tab name";
focus_or_create_tab(tab_name) focus_or_create_tab(tab_name)
}, },
Key::Ctrl('e') => { BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let tab_index = 2; let tab_index = 2;
go_to_tab(tab_index) go_to_tab(tab_index)
}, },
Key::Ctrl('f') => { BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let plugin_url = "file:/path/to/my/plugin.wasm"; let plugin_url = "file:/path/to/my/plugin.wasm";
start_or_reload_plugin(plugin_url) start_or_reload_plugin(plugin_url)
}, },
Key::Ctrl('g') => { BareKey::Char('g') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_file(FileToOpen { open_file(FileToOpen {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
..Default::default() ..Default::default()
}); });
}, },
Key::Ctrl('h') => { BareKey::Char('h') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_file_floating( open_file_floating(
FileToOpen { FileToOpen {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
@ -177,14 +179,14 @@ impl ZellijPlugin for State {
None, None,
); );
}, },
Key::Ctrl('i') => { BareKey::Char('i') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_file(FileToOpen { open_file(FileToOpen {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
line_number: Some(42), line_number: Some(42),
..Default::default() ..Default::default()
}); });
}, },
Key::Ctrl('j') => { BareKey::Char('j') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_file_floating( open_file_floating(
FileToOpen { FileToOpen {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
@ -194,23 +196,23 @@ impl ZellijPlugin for State {
None, None,
); );
}, },
Key::Ctrl('k') => { BareKey::Char('k') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_terminal(std::path::PathBuf::from("/path/to/my/file.rs").as_path()); open_terminal(std::path::PathBuf::from("/path/to/my/file.rs").as_path());
}, },
Key::Ctrl('l') => { BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_terminal_floating( open_terminal_floating(
std::path::PathBuf::from("/path/to/my/file.rs").as_path(), std::path::PathBuf::from("/path/to/my/file.rs").as_path(),
None, None,
); );
}, },
Key::Ctrl('m') => { BareKey::Char('m') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_command_pane(CommandToRun { open_command_pane(CommandToRun {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
args: vec!["arg1".to_owned(), "arg2".to_owned()], args: vec!["arg1".to_owned(), "arg2".to_owned()],
..Default::default() ..Default::default()
}); });
}, },
Key::Ctrl('n') => { BareKey::Char('n') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
open_command_pane_floating( open_command_pane_floating(
CommandToRun { CommandToRun {
path: std::path::PathBuf::from("/path/to/my/file.rs"), path: std::path::PathBuf::from("/path/to/my/file.rs"),
@ -220,51 +222,51 @@ impl ZellijPlugin for State {
None, None,
); );
}, },
Key::Ctrl('o') => { BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
switch_tab_to(1); switch_tab_to(1);
}, },
Key::Ctrl('p') => { BareKey::Char('p') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
hide_self(); hide_self();
}, },
Key::Ctrl('q') => { BareKey::Char('q') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let should_float_if_hidden = false; let should_float_if_hidden = false;
show_self(should_float_if_hidden); show_self(should_float_if_hidden);
}, },
Key::Ctrl('r') => { BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
close_terminal_pane(1); close_terminal_pane(1);
}, },
Key::Ctrl('s') => { BareKey::Char('s') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
close_plugin_pane(1); close_plugin_pane(1);
}, },
Key::Ctrl('t') => { BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let should_float_if_hidden = false; let should_float_if_hidden = false;
focus_terminal_pane(1, should_float_if_hidden); focus_terminal_pane(1, should_float_if_hidden);
}, },
Key::Ctrl('u') => { BareKey::Char('u') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let should_float_if_hidden = false; let should_float_if_hidden = false;
focus_plugin_pane(1, should_float_if_hidden); focus_plugin_pane(1, should_float_if_hidden);
}, },
Key::Ctrl('v') => { BareKey::Char('v') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
rename_terminal_pane(1, "new terminal_pane_name"); rename_terminal_pane(1, "new terminal_pane_name");
}, },
Key::Ctrl('w') => { BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
rename_plugin_pane(1, "new plugin_pane_name"); rename_plugin_pane(1, "new plugin_pane_name");
}, },
Key::Ctrl('x') => { BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
rename_tab(1, "new tab name"); rename_tab(1, "new tab name");
}, },
Key::Ctrl('z') => { BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
go_to_tab_name(&format!("{:?}", self.configuration)); go_to_tab_name(&format!("{:?}", self.configuration));
}, },
Key::Ctrl('1') => { BareKey::Char('1') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
request_permission(&[PermissionType::ReadApplicationState]); request_permission(&[PermissionType::ReadApplicationState]);
}, },
Key::Ctrl('2') => { BareKey::Char('2') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let mut context = BTreeMap::new(); let mut context = BTreeMap::new();
context.insert("user_key_1".to_owned(), "user_value_1".to_owned()); context.insert("user_key_1".to_owned(), "user_value_1".to_owned());
run_command(&["ls", "-l"], context); run_command(&["ls", "-l"], context);
}, },
Key::Ctrl('3') => { BareKey::Char('3') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let mut context = BTreeMap::new(); let mut context = BTreeMap::new();
context.insert("user_key_2".to_owned(), "user_value_2".to_owned()); context.insert("user_key_2".to_owned(), "user_value_2".to_owned());
let mut env_vars = BTreeMap::new(); let mut env_vars = BTreeMap::new();
@ -276,7 +278,7 @@ impl ZellijPlugin for State {
context, context,
); );
}, },
Key::Ctrl('4') => { BareKey::Char('4') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let mut headers = BTreeMap::new(); let mut headers = BTreeMap::new();
let mut context = BTreeMap::new(); let mut context = BTreeMap::new();
let body = vec![1, 2, 3]; let body = vec![1, 2, 3];
@ -292,22 +294,24 @@ impl ZellijPlugin for State {
context, context,
); );
}, },
Key::Ctrl('5') => { BareKey::Char('5') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
switch_session(Some("my_new_session")); switch_session(Some("my_new_session"));
}, },
Key::Ctrl('6') => disconnect_other_clients(), BareKey::Char('6') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
Key::Ctrl('7') => { disconnect_other_clients()
},
BareKey::Char('7') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
switch_session_with_layout( switch_session_with_layout(
Some("my_other_new_session"), Some("my_other_new_session"),
LayoutInfo::BuiltIn("compact".to_owned()), LayoutInfo::BuiltIn("compact".to_owned()),
None, None,
); );
}, },
Key::Ctrl('8') => { BareKey::Char('8') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
let mut file = std::fs::File::create("/host/hi-from-plugin.txt").unwrap(); let mut file = std::fs::File::create("/host/hi-from-plugin.txt").unwrap();
file.write_all(b"Hi there!").unwrap(); file.write_all(b"Hi there!").unwrap();
}, },
Key::Ctrl('9') => { BareKey::Char('9') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
switch_session_with_layout( switch_session_with_layout(
Some("my_other_new_session_with_cwd"), Some("my_other_new_session_with_cwd"),
LayoutInfo::BuiltIn("compact".to_owned()), LayoutInfo::BuiltIn("compact".to_owned()),

View file

@ -175,7 +175,7 @@ impl State {
fn reset_selected_index(&mut self) { fn reset_selected_index(&mut self) {
self.sessions.reset_selected_index(); self.sessions.reset_selected_index();
} }
fn handle_key(&mut self, key: Key) -> bool { fn handle_key(&mut self, key: KeyWithModifier) -> bool {
if self.error.is_some() { if self.error.is_some() {
self.error = None; self.error = None;
return true; return true;
@ -186,197 +186,246 @@ impl State {
ActiveScreen::ResurrectSession => self.handle_resurrect_session_key(key), ActiveScreen::ResurrectSession => self.handle_resurrect_session_key(key),
} }
} }
fn handle_new_session_key(&mut self, key: Key) -> bool { fn handle_new_session_key(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false; let mut should_render = false;
if let Key::Down = key { match key.bare_key {
self.new_session_info.handle_key(key); BareKey::Down if key.has_no_modifiers() => {
should_render = true;
} else if let Key::Up = key {
self.new_session_info.handle_key(key);
should_render = true;
} else if let Key::Char(character) = key {
if character == '\n' {
self.handle_selection();
} else {
self.new_session_info.handle_key(key); self.new_session_info.handle_key(key);
} should_render = true;
should_render = true; },
} else if let Key::Backspace = key { BareKey::Up if key.has_no_modifiers() => {
self.new_session_info.handle_key(key); self.new_session_info.handle_key(key);
should_render = true; should_render = true;
} else if let Key::Ctrl('w') = key { },
self.active_screen = ActiveScreen::NewSession; BareKey::Char(character) if key.has_no_modifiers() => {
should_render = true; if character == '\n' {
} else if let Key::BackTab = key { self.handle_selection();
self.toggle_active_screen(); } else {
should_render = true; self.new_session_info.handle_key(key);
} else if let Key::Ctrl('f') = key { }
let request_id = Uuid::new_v4(); should_render = true;
let mut config = BTreeMap::new(); },
let mut args = BTreeMap::new(); BareKey::Backspace if key.has_no_modifiers() => {
self.request_ids.push(request_id.to_string()); self.new_session_info.handle_key(key);
// we insert this into the config so that a new plugin will be opened (the plugin's should_render = true;
// uniqueness is determined by its name/url as well as its config) },
config.insert("request_id".to_owned(), request_id.to_string()); BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
// we also insert this into the args so that the plugin will have an easier access to self.active_screen = ActiveScreen::NewSession;
// it should_render = true;
args.insert("request_id".to_owned(), request_id.to_string()); },
pipe_message_to_plugin( BareKey::Tab if key.has_no_modifiers() => {
MessageToPlugin::new("filepicker") self.toggle_active_screen();
.with_plugin_url("filepicker") should_render = true;
.with_plugin_config(config) },
.new_plugin_instance_should_have_pane_title( BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
"Select folder for the new session...", let request_id = Uuid::new_v4();
) let mut config = BTreeMap::new();
.with_args(args), let mut args = BTreeMap::new();
); self.request_ids.push(request_id.to_string());
should_render = true; // we insert this into the config so that a new plugin will be opened (the plugin's
} else if let Key::Ctrl('c') = key { // uniqueness is determined by its name/url as well as its config)
self.new_session_info.new_session_folder = None; config.insert("request_id".to_owned(), request_id.to_string());
should_render = true; // we also insert this into the args so that the plugin will have an easier access to
} else if let Key::Esc = key { // it
self.new_session_info.handle_key(key); args.insert("request_id".to_owned(), request_id.to_string());
should_render = true; pipe_message_to_plugin(
MessageToPlugin::new("filepicker")
.with_plugin_url("filepicker")
.with_plugin_config(config)
.new_plugin_instance_should_have_pane_title(
"Select folder for the new session...",
)
.with_args(args),
);
should_render = true;
},
BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.new_session_info.new_session_folder = None;
should_render = true;
},
BareKey::Esc if key.has_no_modifiers() => {
self.new_session_info.handle_key(key);
should_render = true;
},
_ => {},
} }
should_render should_render
} }
fn handle_attach_to_session(&mut self, key: Key) -> bool { fn handle_attach_to_session(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false; let mut should_render = false;
if self.show_kill_all_sessions_warning { if self.show_kill_all_sessions_warning {
if let Key::Char('y') = key { match key.bare_key {
let all_other_sessions = self.sessions.all_other_sessions(); BareKey::Char('y') if key.has_no_modifiers() => {
kill_sessions(&all_other_sessions); let all_other_sessions = self.sessions.all_other_sessions();
self.reset_selected_index(); kill_sessions(&all_other_sessions);
self.search_term.clear(); self.reset_selected_index();
self.sessions self.search_term.clear();
.update_search_term(&self.search_term, &self.colors); self.sessions
self.show_kill_all_sessions_warning = false .update_search_term(&self.search_term, &self.colors);
} else if let Key::Char('n') | Key::Esc | Key::Ctrl('c') = key { self.show_kill_all_sessions_warning = false;
self.show_kill_all_sessions_warning = false should_render = true;
},
BareKey::Char('n') | BareKey::Esc if key.has_no_modifiers() => {
self.show_kill_all_sessions_warning = false;
should_render = true;
},
BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.show_kill_all_sessions_warning = false;
should_render = true;
},
_ => {},
} }
should_render = true; } else {
} else if let Key::Right = key { match key.bare_key {
self.sessions.result_expand(); BareKey::Right if key.has_no_modifiers() => {
should_render = true; self.sessions.result_expand();
} else if let Key::Left = key { should_render = true;
self.sessions.result_shrink(); },
should_render = true; BareKey::Left if key.has_no_modifiers() => {
} else if let Key::Down = key { self.sessions.result_shrink();
self.sessions.move_selection_down(); should_render = true;
should_render = true; },
} else if let Key::Up = key { BareKey::Down if key.has_no_modifiers() => {
self.sessions.move_selection_up(); self.sessions.move_selection_down();
should_render = true; should_render = true;
} else if let Key::Char(character) = key { },
if character == '\n' { BareKey::Up if key.has_no_modifiers() => {
self.handle_selection(); self.sessions.move_selection_up();
} else if let Some(new_session_name) = self.renaming_session_name.as_mut() { should_render = true;
new_session_name.push(character); },
} else { BareKey::Char(character) if key.has_no_modifiers() => {
self.search_term.push(character); if character == '\n' {
self.sessions self.handle_selection();
.update_search_term(&self.search_term, &self.colors); } else if let Some(new_session_name) = self.renaming_session_name.as_mut() {
} new_session_name.push(character);
should_render = true; } else {
} else if let Key::Backspace = key { self.search_term.push(character);
if let Some(new_session_name) = self.renaming_session_name.as_mut() { self.sessions
if new_session_name.is_empty() { .update_search_term(&self.search_term, &self.colors);
self.renaming_session_name = None; }
} else { should_render = true;
new_session_name.pop(); },
} BareKey::Backspace if key.has_no_modifiers() => {
} else { if let Some(new_session_name) = self.renaming_session_name.as_mut() {
self.search_term.pop(); if new_session_name.is_empty() {
self.sessions self.renaming_session_name = None;
.update_search_term(&self.search_term, &self.colors); } else {
} new_session_name.pop();
should_render = true; }
} else if let Key::Ctrl('w') = key { } else {
self.active_screen = ActiveScreen::NewSession; self.search_term.pop();
should_render = true; self.sessions
} else if let Key::Ctrl('r') = key { .update_search_term(&self.search_term, &self.colors);
self.renaming_session_name = Some(String::new()); }
should_render = true; should_render = true;
} else if let Key::Delete = key { },
if let Some(selected_session_name) = self.sessions.get_selected_session_name() { BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
kill_sessions(&[selected_session_name]); self.active_screen = ActiveScreen::NewSession;
self.reset_selected_index(); should_render = true;
self.search_term.clear(); },
self.sessions BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
.update_search_term(&self.search_term, &self.colors); self.renaming_session_name = Some(String::new());
} else { should_render = true;
self.show_error("Must select session before killing it."); },
} BareKey::Delete if key.has_no_modifiers() => {
should_render = true; if let Some(selected_session_name) = self.sessions.get_selected_session_name() {
} else if let Key::Ctrl('d') = key { kill_sessions(&[selected_session_name]);
let all_other_sessions = self.sessions.all_other_sessions(); self.reset_selected_index();
if all_other_sessions.is_empty() { self.search_term.clear();
self.show_error("No other sessions to kill. Quit to kill the current one."); self.sessions
} else { .update_search_term(&self.search_term, &self.colors);
self.show_kill_all_sessions_warning = true; } else {
} self.show_error("Must select session before killing it.");
should_render = true; }
} else if let Key::Ctrl('x') = key { should_render = true;
disconnect_other_clients(); },
} else if let Key::Ctrl('c') = key { BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
if !self.search_term.is_empty() { let all_other_sessions = self.sessions.all_other_sessions();
self.search_term.clear(); if all_other_sessions.is_empty() {
self.sessions self.show_error("No other sessions to kill. Quit to kill the current one.");
.update_search_term(&self.search_term, &self.colors); } else {
self.reset_selected_index(); self.show_kill_all_sessions_warning = true;
} else if !self.is_welcome_screen { }
self.reset_selected_index(); should_render = true;
hide_self(); },
} BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
should_render = true; disconnect_other_clients()
} else if let Key::BackTab = key { },
self.toggle_active_screen(); BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
should_render = true; if !self.search_term.is_empty() {
} else if let Key::Esc = key { self.search_term.clear();
if self.renaming_session_name.is_some() { self.sessions
self.renaming_session_name = None; .update_search_term(&self.search_term, &self.colors);
should_render = true; self.reset_selected_index();
} else if !self.is_welcome_screen { } else if !self.is_welcome_screen {
hide_self(); self.reset_selected_index();
hide_self();
}
should_render = true;
},
BareKey::Tab if key.has_no_modifiers() => {
self.toggle_active_screen();
should_render = true;
},
BareKey::Esc if key.has_no_modifiers() => {
if self.renaming_session_name.is_some() {
self.renaming_session_name = None;
should_render = true;
} else if !self.is_welcome_screen {
hide_self();
}
},
_ => {},
} }
} }
should_render should_render
} }
fn handle_resurrect_session_key(&mut self, key: Key) -> bool { fn handle_resurrect_session_key(&mut self, key: KeyWithModifier) -> bool {
let mut should_render = false; let mut should_render = false;
if let Key::Down = key { match key.bare_key {
self.resurrectable_sessions.move_selection_down(); BareKey::Down if key.has_no_modifiers() => {
should_render = true; self.resurrectable_sessions.move_selection_down();
} else if let Key::Up = key { should_render = true;
self.resurrectable_sessions.move_selection_up(); },
should_render = true; BareKey::Up if key.has_no_modifiers() => {
} else if let Key::Char(character) = key { self.resurrectable_sessions.move_selection_up();
if character == '\n' { should_render = true;
self.handle_selection(); },
} else { BareKey::Char(character) if key.has_no_modifiers() => {
self.resurrectable_sessions.handle_character(character); if character == '\n' {
} self.handle_selection();
should_render = true; } else {
} else if let Key::Backspace = key { self.resurrectable_sessions.handle_character(character);
self.resurrectable_sessions.handle_backspace(); }
should_render = true; should_render = true;
} else if let Key::Ctrl('w') = key { },
self.active_screen = ActiveScreen::NewSession; BareKey::Backspace if key.has_no_modifiers() => {
should_render = true; self.resurrectable_sessions.handle_backspace();
} else if let Key::BackTab = key { should_render = true;
self.toggle_active_screen(); },
should_render = true; BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
} else if let Key::Delete = key { self.active_screen = ActiveScreen::NewSession;
self.resurrectable_sessions.delete_selected_session(); should_render = true;
should_render = true; },
} else if let Key::Ctrl('d') = key { BareKey::Tab if key.has_no_modifiers() => {
self.resurrectable_sessions self.toggle_active_screen();
.show_delete_all_sessions_warning(); should_render = true;
should_render = true; },
} else if let Key::Esc = key { BareKey::Delete if key.has_no_modifiers() => {
if !self.is_welcome_screen { self.resurrectable_sessions.delete_selected_session();
hide_self(); should_render = true;
} },
BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.resurrectable_sessions
.show_delete_all_sessions_warning();
should_render = true;
},
BareKey::Esc if key.has_no_modifiers() => {
if !self.is_welcome_screen {
hide_self();
}
},
_ => {},
} }
should_render should_render
} }

View file

@ -70,21 +70,24 @@ impl NewSessionInfo {
}, },
} }
} }
pub fn handle_key(&mut self, key: Key) { pub fn handle_key(&mut self, key: KeyWithModifier) {
match key { match key.bare_key {
Key::Backspace => { BareKey::Backspace if key.has_no_modifiers() => {
self.handle_backspace(); self.handle_backspace();
}, },
Key::Ctrl('c') | Key::Esc => { BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.handle_break(); self.handle_break();
}, },
Key::Char(character) => { BareKey::Esc if key.has_no_modifiers() => {
self.handle_break();
},
BareKey::Char(character) if key.has_no_modifiers() => {
self.add_char(character); self.add_char(character);
}, },
Key::Up => { BareKey::Up if key.has_no_modifiers() => {
self.move_selection_up(); self.move_selection_up();
}, },
Key::Down => { BareKey::Down if key.has_no_modifiers() => {
self.move_selection_down(); self.move_selection_down();
}, },
_ => {}, _ => {},

View file

@ -4,14 +4,14 @@ use zellij_tile::prelude::*;
use crate::color_elements; use crate::color_elements;
use crate::{ use crate::{
action_key, action_key_group, get_common_modifier, style_key_with_modifier, TO_NORMAL, action_key, action_key_group, get_common_modifiers, style_key_with_modifier, TO_NORMAL,
}; };
use crate::{ColoredElements, LinePart}; use crate::{ColoredElements, LinePart};
struct KeyShortcut { struct KeyShortcut {
mode: KeyMode, mode: KeyMode,
action: KeyAction, action: KeyAction,
key: Option<Key>, key: Option<KeyWithModifier>,
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@ -35,7 +35,7 @@ enum KeyMode {
} }
impl KeyShortcut { impl KeyShortcut {
pub fn new(mode: KeyMode, action: KeyAction, key: Option<Key>) -> Self { pub fn new(mode: KeyMode, action: KeyAction, key: Option<KeyWithModifier>) -> Self {
KeyShortcut { mode, action, key } KeyShortcut { mode, action, key }
} }
@ -52,25 +52,36 @@ impl KeyShortcut {
KeyAction::Tmux => String::from("TMUX"), KeyAction::Tmux => String::from("TMUX"),
} }
} }
pub fn letter_shortcut(&self, with_prefix: bool) -> String { pub fn with_shortened_modifiers(&self, common_modifiers: &Vec<KeyModifier>) -> String {
let key = match self.key { let key = match &self.key {
Some(k) => k, Some(k) => k.strip_common_modifiers(common_modifiers),
None => return String::from("?"), None => return String::from("?"),
}; };
if with_prefix { let shortened_modifiers = key
.key_modifiers
.iter()
.map(|m| match m {
KeyModifier::Ctrl => "^C",
KeyModifier::Alt => "^A",
KeyModifier::Super => "^Su",
KeyModifier::Shift => "^Sh",
_ => "",
})
.collect::<Vec<_>>()
.join("-");
if shortened_modifiers.is_empty() {
format!("{}", key) format!("{}", key)
} else { } else {
match key { format!("{} {}", shortened_modifiers, key.bare_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("??"),
}
} }
} }
pub fn letter_shortcut(&self, common_modifiers: &Vec<KeyModifier>) -> String {
let key = match &self.key {
Some(k) => k.strip_common_modifiers(common_modifiers),
None => return String::from("?"),
};
format!("{}", key)
}
} }
/// Generate long mode shortcut tile. /// Generate long mode shortcut tile.
@ -96,14 +107,15 @@ fn long_mode_shortcut(
key: &KeyShortcut, key: &KeyShortcut,
palette: ColoredElements, palette: ColoredElements,
separator: &str, separator: &str,
shared_super: bool, common_modifiers: &Vec<KeyModifier>,
first_tile: bool, first_tile: bool,
) -> LinePart { ) -> LinePart {
let key_hint = key.full_text(); let key_hint = key.full_text();
let has_common_modifiers = !common_modifiers.is_empty();
let key_binding = match (&key.mode, &key.key) { let key_binding = match (&key.mode, &key.key) {
(KeyMode::Disabled, None) => "".to_string(), (KeyMode::Disabled, None) => "".to_string(),
(_, None) => return LinePart::default(), (_, None) => return LinePart::default(),
(_, Some(_)) => key.letter_shortcut(!shared_super), (_, Some(_)) => key.letter_shortcut(common_modifiers),
}; };
let colors = match key.mode { let colors = match key.mode {
@ -112,7 +124,59 @@ fn long_mode_shortcut(
KeyMode::Selected => palette.selected, KeyMode::Selected => palette.selected,
KeyMode::Disabled => palette.disabled, KeyMode::Disabled => palette.disabled,
}; };
let start_separator = if !shared_super && first_tile { let start_separator = if !has_common_modifiers && first_tile {
""
} else {
separator
};
let prefix_separator = colors.prefix_separator.paint(start_separator);
let char_left_separator = colors.char_left_separator.paint(" <".to_string());
let char_shortcut = colors.char_shortcut.paint(key_binding.to_string());
let char_right_separator = colors.char_right_separator.paint("> ".to_string());
let styled_text = colors.styled_text.paint(format!("{} ", key_hint));
let suffix_separator = colors.suffix_separator.paint(separator);
LinePart {
part: ANSIStrings(&[
prefix_separator,
char_left_separator,
char_shortcut,
char_right_separator,
styled_text,
suffix_separator,
])
.to_string(),
len: start_separator.chars().count() // Separator
+ 2 // " <"
+ key_binding.chars().count() // Key binding
+ 2 // "> "
+ key_hint.chars().count() // Key hint (mode)
+ 1 // " "
+ separator.chars().count(), // Separator
}
}
fn shortened_modifier_shortcut(
key: &KeyShortcut,
palette: ColoredElements,
separator: &str,
common_modifiers: &Vec<KeyModifier>,
first_tile: bool,
) -> LinePart {
let key_hint = key.full_text();
let has_common_modifiers = !common_modifiers.is_empty();
let key_binding = match (&key.mode, &key.key) {
(KeyMode::Disabled, None) => "".to_string(),
(_, None) => return LinePart::default(),
(_, Some(_)) => key.with_shortened_modifiers(common_modifiers),
};
let colors = match key.mode {
KeyMode::Unselected => palette.unselected,
KeyMode::UnselectedAlternate => palette.unselected_alternate,
KeyMode::Selected => palette.selected,
KeyMode::Disabled => palette.disabled,
};
let start_separator = if !has_common_modifiers && first_tile {
"" ""
} else { } else {
separator separator
@ -165,13 +229,14 @@ fn short_mode_shortcut(
key: &KeyShortcut, key: &KeyShortcut,
palette: ColoredElements, palette: ColoredElements,
separator: &str, separator: &str,
shared_super: bool, common_modifiers: &Vec<KeyModifier>,
first_tile: bool, first_tile: bool,
) -> LinePart { ) -> LinePart {
let has_common_modifiers = !common_modifiers.is_empty();
let key_binding = match (&key.mode, &key.key) { let key_binding = match (&key.mode, &key.key) {
(KeyMode::Disabled, None) => "".to_string(), (KeyMode::Disabled, None) => "".to_string(),
(_, None) => return LinePart::default(), (_, None) => return LinePart::default(),
(_, Some(_)) => key.letter_shortcut(!shared_super), (_, Some(_)) => key.letter_shortcut(common_modifiers),
}; };
let colors = match key.mode { let colors = match key.mode {
@ -180,7 +245,7 @@ fn short_mode_shortcut(
KeyMode::Selected => palette.selected, KeyMode::Selected => palette.selected,
KeyMode::Disabled => palette.disabled, KeyMode::Disabled => palette.disabled,
}; };
let start_separator = if !shared_super && first_tile { let start_separator = if !has_common_modifiers && first_tile {
"" ""
} else { } else {
separator separator
@ -206,11 +271,23 @@ fn key_indicators(
mode_info: &ModeInfo, mode_info: &ModeInfo,
) -> LinePart { ) -> LinePart {
// Print full-width hints // Print full-width hints
let mut line_part = superkey(palette, separator, mode_info); let (shared_modifiers, mut line_part) = superkey(palette, separator, mode_info);
let shared_super = line_part.len > 0; for key in keys {
for ctrl_key in keys {
let line_empty = line_part.len == 0; let line_empty = line_part.len == 0;
let key = long_mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); let key = long_mode_shortcut(key, palette, separator, &shared_modifiers, line_empty);
line_part.part = format!("{}{}", line_part.part, key.part);
line_part.len += key.len;
}
if line_part.len < max_len {
return line_part;
}
// Full-width doesn't fit, try shortened modifiers (eg. "^C" instead of "Ctrl")
line_part = superkey(palette, separator, mode_info).1;
for key in keys {
let line_empty = line_part.len == 0;
let key =
shortened_modifier_shortcut(key, palette, separator, &shared_modifiers, line_empty);
line_part.part = format!("{}{}", line_part.part, key.part); line_part.part = format!("{}{}", line_part.part, key.part);
line_part.len += key.len; line_part.len += key.len;
} }
@ -219,11 +296,10 @@ fn key_indicators(
} }
// Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions)
line_part = superkey(palette, separator, mode_info); line_part = superkey(palette, separator, mode_info).1;
let shared_super = line_part.len > 0; for key in keys {
for ctrl_key in keys {
let line_empty = line_part.len == 0; let line_empty = line_part.len == 0;
let key = short_mode_shortcut(ctrl_key, palette, separator, shared_super, line_empty); let key = short_mode_shortcut(key, palette, separator, &shared_modifiers, line_empty);
line_part.part = format!("{}{}", line_part.part, key.part); line_part.part = format!("{}{}", line_part.part, key.part);
line_part.len += key.len; line_part.len += key.len;
} }
@ -345,7 +421,7 @@ fn swap_layout_status(
/// to get back to normal mode from any input mode, but they aren't of interest when searching /// to get back to normal mode from any input mode, but they aren't of interest when searching
/// for the super key. If for any input mode the user has bound only these keys to switching back /// for the super key. If for any input mode the user has bound only these keys to switching back
/// to `InputMode::Normal`, a '?' will be displayed as keybinding instead. /// to `InputMode::Normal`, a '?' will be displayed as keybinding instead.
pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<Key> { pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<KeyWithModifier> {
mode_info mode_info
.get_mode_keybinds() .get_mode_keybinds()
.iter() .iter()
@ -355,7 +431,19 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<Key> {
Some(vac) => { Some(vac) => {
// We ignore certain "default" keybindings that switch back to normal InputMode. // We ignore certain "default" keybindings that switch back to normal InputMode.
// These include: ' ', '\n', 'Esc' // These include: ' ', '\n', 'Esc'
if matches!(key, Key::Char(' ') | Key::Char('\n') | Key::Esc) { if matches!(
key,
KeyWithModifier {
bare_key: BareKey::Char(' '),
..
} | KeyWithModifier {
bare_key: BareKey::Enter,
..
} | KeyWithModifier {
bare_key: BareKey::Esc,
..
}
) {
return None; return None;
} }
if let actions::Action::SwitchToMode(mode) = vac { if let actions::Action::SwitchToMode(mode) = vac {
@ -368,12 +456,12 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<Key> {
| InputMode::Resize | InputMode::Resize
| InputMode::Move | InputMode::Move
| InputMode::Scroll | InputMode::Scroll
| InputMode::Session => Some(*key), | InputMode::Session => Some(key.clone()),
_ => None, _ => None,
}; };
} }
if let actions::Action::Quit = vac { if let actions::Action::Quit = vac {
return Some(*key); return Some(key.clone());
} }
// Not a `SwitchToMode` or `Quit` action, ignore // Not a `SwitchToMode` or `Quit` action, ignore
None None
@ -382,36 +470,69 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec<Key> {
.collect() .collect()
} }
pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { pub fn superkey(
palette: ColoredElements,
separator: &str,
mode_info: &ModeInfo,
) -> (Vec<KeyModifier>, LinePart) {
// Find a common modifier if any // Find a common modifier if any
let prefix_text = match get_common_modifier(mode_switch_keys(mode_info).iter().collect()) { let common_modifiers = get_common_modifiers(mode_switch_keys(mode_info).iter().collect());
Some(text) => { if common_modifiers.is_empty() {
if mode_info.capabilities.arrow_fonts { return (common_modifiers, LinePart::default());
// Add extra space in simplified ui }
format!(" {} + ", text)
} else { let prefix_text = if mode_info.capabilities.arrow_fonts {
format!(" {} +", text) // Add extra space in simplified ui
} format!(
}, " {} + ",
_ => return LinePart::default(), common_modifiers
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-")
)
} else {
format!(
" {} +",
common_modifiers
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-")
)
}; };
let prefix = palette.superkey_prefix.paint(&prefix_text); let prefix = palette.superkey_prefix.paint(&prefix_text);
let suffix_separator = palette.superkey_suffix_separator.paint(separator); let suffix_separator = palette.superkey_suffix_separator.paint(separator);
LinePart { (
part: ANSIStrings(&[prefix, suffix_separator]).to_string(), common_modifiers,
len: prefix_text.chars().count() + separator.chars().count(), LinePart {
} part: ANSIStrings(&[prefix, suffix_separator]).to_string(),
len: prefix_text.chars().count() + separator.chars().count(),
},
)
} }
pub fn to_char(kv: Vec<Key>) -> Option<Key> { pub fn to_char(kv: Vec<KeyWithModifier>) -> Option<KeyWithModifier> {
let key = kv let key = kv
.iter() .iter()
.filter(|key| { .filter(|key| {
// These are general "keybindings" to get back to normal, they aren't interesting here. // These are general "keybindings" to get back to normal, they aren't interesting here.
!matches!(key, Key::Char('\n') | Key::Char(' ') | Key::Esc) !matches!(
key,
KeyWithModifier {
bare_key: BareKey::Enter,
..
} | KeyWithModifier {
bare_key: BareKey::Char(' '),
..
} | KeyWithModifier {
bare_key: BareKey::Esc,
..
}
)
}) })
.collect::<Vec<&Key>>() .collect::<Vec<&KeyWithModifier>>()
.into_iter() .into_iter()
.next(); .next();
// Maybe the user bound one of the ignored keys? // Maybe the user bound one of the ignored keys?
@ -593,10 +714,14 @@ mod tests {
#[test] #[test]
fn long_mode_shortcut_selected_with_binding() { fn long_mode_shortcut_selected_with_binding() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -608,11 +733,11 @@ mod tests {
let key = KeyShortcut::new( let key = KeyShortcut::new(
KeyMode::Unselected, KeyMode::Unselected,
KeyAction::Session, KeyAction::Session,
Some(Key::Char('0')), Some(KeyWithModifier::new(BareKey::Char('0'))),
); );
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -624,11 +749,11 @@ mod tests {
let key = KeyShortcut::new( let key = KeyShortcut::new(
KeyMode::UnselectedAlternate, KeyMode::UnselectedAlternate,
KeyAction::Session, KeyAction::Session,
Some(Key::Char('0')), Some(KeyWithModifier::new(BareKey::Char('0'))),
); );
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -640,7 +765,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "".to_string()); assert_eq!(ret, "".to_string());
@ -649,10 +774,14 @@ mod tests {
#[test] #[test]
// First tile doesn't print a starting separator // First tile doesn't print a starting separator
fn long_mode_shortcut_selected_with_binding_first_tile() { fn long_mode_shortcut_selected_with_binding_first_tile() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, true); let ret = long_mode_shortcut(&key, color, "+", &vec![], true);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, " <0> SESSION +".to_string()); assert_eq!(ret, " <0> SESSION +".to_string());
@ -661,10 +790,14 @@ mod tests {
#[test] #[test]
// Modifier is the superkey, mustn't appear in angled brackets // Modifier is the superkey, mustn't appear in angled brackets
fn long_mode_shortcut_selected_with_ctrl_binding_shared_superkey() { fn long_mode_shortcut_selected_with_ctrl_binding_shared_superkey() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", true, false); let ret = long_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -673,22 +806,30 @@ mod tests {
#[test] #[test]
// Modifier must be in the angled brackets // Modifier must be in the angled brackets
fn long_mode_shortcut_selected_with_ctrl_binding_no_shared_superkey() { fn long_mode_shortcut_selected_with_ctrl_binding_no_shared_superkey() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <Ctrl+0> SESSION +".to_string()); assert_eq!(ret, "+ <Ctrl 0> SESSION +".to_string());
} }
#[test] #[test]
// Must be displayed as usual, but it is styled to be greyed out which we don't test here // Must be displayed as usual, but it is styled to be greyed out which we don't test here
fn long_mode_shortcut_disabled_with_binding() { fn long_mode_shortcut_disabled_with_binding() {
let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Disabled,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -700,7 +841,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", false, false); let ret = long_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <> SESSION +".to_string()); assert_eq!(ret, "+ <> SESSION +".to_string());
@ -711,10 +852,14 @@ mod tests {
// Note that when "shared_super" is true, the tile **cannot** be the first on the line, so we // Note that when "shared_super" is true, the tile **cannot** be the first on the line, so we
// ignore **first** here. // ignore **first** here.
fn long_mode_shortcut_selected_with_ctrl_binding_and_shared_super_and_first_tile() { fn long_mode_shortcut_selected_with_ctrl_binding_and_shared_super_and_first_tile() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()),
);
let color = colored_elements(); let color = colored_elements();
let ret = long_mode_shortcut(&key, color, "+", true, true); let ret = long_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], true);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ <0> SESSION +".to_string()); assert_eq!(ret, "+ <0> SESSION +".to_string());
@ -722,10 +867,14 @@ mod tests {
#[test] #[test]
fn short_mode_shortcut_selected_with_binding() { fn short_mode_shortcut_selected_with_binding() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ 0 +".to_string()); assert_eq!(ret, "+ 0 +".to_string());
@ -733,21 +882,29 @@ mod tests {
#[test] #[test]
fn short_mode_shortcut_selected_with_ctrl_binding_no_shared_super() { fn short_mode_shortcut_selected_with_ctrl_binding_no_shared_super() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()),
);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ Ctrl+0 +".to_string()); assert_eq!(ret, "+ Ctrl 0 +".to_string());
} }
#[test] #[test]
fn short_mode_shortcut_selected_with_ctrl_binding_shared_super() { fn short_mode_shortcut_selected_with_ctrl_binding_shared_super() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Ctrl('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()),
);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", true, false); let ret = short_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ 0 +".to_string()); assert_eq!(ret, "+ 0 +".to_string());
@ -755,10 +912,14 @@ mod tests {
#[test] #[test]
fn short_mode_shortcut_selected_with_binding_first_tile() { fn short_mode_shortcut_selected_with_binding_first_tile() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, true); let ret = short_mode_shortcut(&key, color, "+", &vec![], true);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, " 0 +".to_string()); assert_eq!(ret, " 0 +".to_string());
@ -769,11 +930,11 @@ mod tests {
let key = KeyShortcut::new( let key = KeyShortcut::new(
KeyMode::Unselected, KeyMode::Unselected,
KeyAction::Session, KeyAction::Session,
Some(Key::Char('0')), Some(KeyWithModifier::new(BareKey::Char('0'))),
); );
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ 0 +".to_string()); assert_eq!(ret, "+ 0 +".to_string());
@ -784,11 +945,11 @@ mod tests {
let key = KeyShortcut::new( let key = KeyShortcut::new(
KeyMode::UnselectedAlternate, KeyMode::UnselectedAlternate,
KeyAction::Session, KeyAction::Session,
Some(Key::Char('0')), Some(KeyWithModifier::new(BareKey::Char('0'))),
); );
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ 0 +".to_string()); assert_eq!(ret, "+ 0 +".to_string());
@ -796,10 +957,14 @@ mod tests {
#[test] #[test]
fn short_mode_shortcut_disabled_with_binding() { fn short_mode_shortcut_disabled_with_binding() {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, Some(Key::Char('0'))); let key = KeyShortcut::new(
KeyMode::Selected,
KeyAction::Session,
Some(KeyWithModifier::new(BareKey::Char('0'))),
);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "+ 0 +".to_string()); assert_eq!(ret, "+ 0 +".to_string());
@ -810,7 +975,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "".to_string()); assert_eq!(ret, "".to_string());
@ -821,7 +986,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::Unselected, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::Unselected, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "".to_string()); assert_eq!(ret, "".to_string());
@ -832,7 +997,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "".to_string()); assert_eq!(ret, "".to_string());
@ -843,7 +1008,7 @@ mod tests {
let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None);
let color = colored_elements(); let color = colored_elements();
let ret = short_mode_shortcut(&key, color, "+", false, false); let ret = short_mode_shortcut(&key, color, "+", &vec![], false);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, "".to_string()); assert_eq!(ret, "".to_string());
@ -857,9 +1022,9 @@ mod tests {
mode: InputMode::Normal, mode: InputMode::Normal,
keybinds : vec![ keybinds : vec![
(InputMode::Normal, vec![ (InputMode::Normal, vec![
(Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]),
(Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]),
(Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]),
]), ]),
], ],
..ModeInfo::default() ..ModeInfo::default()
@ -881,9 +1046,9 @@ mod tests {
mode: InputMode::Normal, mode: InputMode::Normal,
keybinds : vec![ keybinds : vec![
(InputMode::Normal, vec![ (InputMode::Normal, vec![
(Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]),
(Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]),
(Key::Char('c'), vec![Action::SwitchToMode(InputMode::Move)]), (KeyWithModifier::new(BareKey::Char('c')), vec![Action::SwitchToMode(InputMode::Move)]),
]), ]),
], ],
..ModeInfo::default() ..ModeInfo::default()
@ -894,7 +1059,7 @@ mod tests {
assert_eq!( assert_eq!(
ret, ret,
" <Ctrl+a> PANE >> <Ctrl+b> RESIZE >> <c> MOVE >".to_string() " <Ctrl a> PANE >> <Ctrl b> RESIZE >> <c> MOVE >".to_string()
); );
} }
@ -905,11 +1070,11 @@ mod tests {
mode: InputMode::Normal, mode: InputMode::Normal,
keybinds : vec![ keybinds : vec![
(InputMode::Normal, vec![ (InputMode::Normal, vec![
(Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Locked)]),
(Key::Backspace, vec![Action::SwitchToMode(InputMode::Pane)]), (KeyWithModifier::new(BareKey::Backspace), vec![Action::SwitchToMode(InputMode::Pane)]),
(Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]), (KeyWithModifier::new(BareKey::Enter), vec![Action::SwitchToMode(InputMode::Tab)]),
(Key::Char('\t'), vec![Action::SwitchToMode(InputMode::Resize)]), (KeyWithModifier::new(BareKey::Tab), vec![Action::SwitchToMode(InputMode::Resize)]),
(Key::Left, vec![Action::SwitchToMode(InputMode::Move)]), (KeyWithModifier::new(BareKey::Left), vec![Action::SwitchToMode(InputMode::Move)]),
]), ]),
], ],
..ModeInfo::default() ..ModeInfo::default()
@ -920,7 +1085,7 @@ mod tests {
assert_eq!( assert_eq!(
ret, ret,
" <Ctrl+a> LOCK >> <BACKSPACE> PANE >> <ENTER> TAB >> <TAB> RESIZE >> <←> MOVE >" " <Ctrl a> LOCK >> <BACKSPACE> PANE >> <ENTER> TAB >> <TAB> RESIZE >> <←> MOVE >"
.to_string() .to_string()
); );
} }
@ -932,11 +1097,11 @@ mod tests {
mode: InputMode::Normal, mode: InputMode::Normal,
keybinds : vec![ keybinds : vec![
(InputMode::Normal, vec![ (InputMode::Normal, vec![
(Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Locked)]),
(Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Pane)]), (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]),
(Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Tab)]), (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Tab)]),
(Key::Ctrl('d'), vec![Action::SwitchToMode(InputMode::Resize)]), (KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]),
(Key::Ctrl('e'), vec![Action::SwitchToMode(InputMode::Move)]), (KeyWithModifier::new(BareKey::Char('e')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]),
]), ]),
], ],
..ModeInfo::default() ..ModeInfo::default()
@ -955,9 +1120,9 @@ mod tests {
mode: InputMode::Normal, mode: InputMode::Normal,
keybinds : vec![ keybinds : vec![
(InputMode::Normal, vec![ (InputMode::Normal, vec![
(Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]),
(Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]),
(Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]),
]), ]),
], ],
..ModeInfo::default() ..ModeInfo::default()

View file

@ -324,30 +324,18 @@ impl State {
} }
} }
/// Get a common modifier key from a key vector. pub fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec<KeyModifier> {
/// if keyvec.is_empty() {
/// Iterates over all keys and returns any found common modifier key. Possible modifiers that will return vec![];
/// be detected are "Ctrl" and "Alt".
pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option<String> {
let mut modifier = "";
let mut new_modifier;
for key in keyvec.iter() {
match key {
Key::Ctrl(_) | Key::CtrlF(_) => new_modifier = "Ctrl",
Key::Alt(_) | Key::AltF(_) => new_modifier = "Alt",
_ => return None,
}
if modifier.is_empty() {
modifier = new_modifier;
} else if modifier != new_modifier {
// Prefix changed!
return None;
}
} }
match modifier.is_empty() { let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone();
true => None, for key in keyvec {
false => Some(modifier.to_string()), common_modifiers = common_modifiers
.intersection(&key.key_modifiers)
.cloned()
.collect();
} }
common_modifiers.into_iter().collect()
} }
/// Get key from action pattern(s). /// Get key from action pattern(s).
@ -356,7 +344,10 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option<String> {
/// all keybindings for the current mode and one or more `p` patterns which match a sequence of /// all keybindings for the current mode and one or more `p` patterns which match a sequence of
/// actions to search for. If within the keymap a sequence of actions matching `p` is found, all /// actions to search for. If within the keymap a sequence of actions matching `p` is found, all
/// keys that trigger the action pattern are returned as vector of `Vec<Key>`. /// keys that trigger the action pattern are returned as vector of `Vec<Key>`.
pub fn action_key(keymap: &[(Key, Vec<Action>)], action: &[Action]) -> Vec<Key> { pub fn action_key(
keymap: &[(KeyWithModifier, Vec<Action>)],
action: &[Action],
) -> Vec<KeyWithModifier> {
keymap keymap
.iter() .iter()
.filter_map(|(key, acvec)| { .filter_map(|(key, acvec)| {
@ -367,18 +358,21 @@ pub fn action_key(keymap: &[(Key, Vec<Action>)], action: &[Action]) -> Vec<Key>
.count(); .count();
if matching == acvec.len() && matching == action.len() { if matching == acvec.len() && matching == action.len() {
Some(*key) Some(key.clone())
} else { } else {
None None
} }
}) })
.collect::<Vec<Key>>() .collect::<Vec<KeyWithModifier>>()
} }
/// Get multiple keys for multiple actions. /// Get multiple keys for multiple actions.
/// ///
/// An extension of [`action_key`] that iterates over all action tuples and collects the results. /// An extension of [`action_key`] that iterates over all action tuples and collects the results.
pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) -> Vec<Key> { pub fn action_key_group(
keymap: &[(KeyWithModifier, Vec<Action>)],
actions: &[&[Action]],
) -> Vec<KeyWithModifier> {
let mut ret = vec![]; let mut ret = vec![];
for action in actions { for action in actions {
ret.extend(action_key(keymap, action)); ret.extend(action_key(keymap, action));
@ -406,11 +400,10 @@ pub fn action_key_group(keymap: &[(Key, Vec<Action>)], actions: &[&[Action]]) ->
/// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`] /// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`]
/// type. /// type.
pub fn style_key_with_modifier( pub fn style_key_with_modifier(
keyvec: &[Key], keyvec: &[KeyWithModifier],
palette: &Palette, palette: &Palette,
background: Option<PaletteColor>, background: Option<PaletteColor>,
) -> Vec<ANSIString<'static>> { ) -> Vec<ANSIString<'static>> {
// Nothing to do, quit...
if keyvec.is_empty() { if keyvec.is_empty() {
return vec![]; return vec![];
} }
@ -423,12 +416,18 @@ pub fn style_key_with_modifier(
let orange_color = palette_match!(palette.orange); let orange_color = palette_match!(palette.orange);
let mut ret = vec![]; let mut ret = vec![];
// Prints modifier key let common_modifiers = get_common_modifiers(keyvec.iter().collect());
let modifier_str = match get_common_modifier(keyvec.iter().collect()) {
Some(modifier) => modifier, // let modifier_str = match get_common_modifier(keyvec.iter().collect()) {
None => "".to_string(), // Some(modifier) => modifier,
}; // None => "".to_string(),
let no_modifier = modifier_str.is_empty(); // };
let no_common_modifier = common_modifiers.is_empty();
let modifier_str = common_modifiers
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-");
let painted_modifier = if modifier_str.is_empty() { let painted_modifier = if modifier_str.is_empty() {
Style::new().paint("") Style::new().paint("")
} else { } else {
@ -446,7 +445,7 @@ pub fn style_key_with_modifier(
ret.push(painted_modifier); ret.push(painted_modifier);
// Prints key group start // Prints key group start
let group_start_str = if no_modifier { "<" } else { " + <" }; let group_start_str = if no_common_modifier { "<" } else { " + <" };
if let Some(background) = background { if let Some(background) = background {
let background = palette_match!(background); let background = palette_match!(background);
ret.push( ret.push(
@ -463,15 +462,20 @@ pub fn style_key_with_modifier(
let key = keyvec let key = keyvec
.iter() .iter()
.map(|key| { .map(|key| {
if no_modifier { if no_common_modifier {
format!("{}", key) format!("{}", key)
} else { } else {
match key { let key_modifier_for_key = key
Key::Ctrl(c) => format!("{}", Key::Char(*c)), .key_modifiers
Key::CtrlF(n) => format!("{}", Key::F(*n)), .iter()
Key::Alt(c) => format!("{}", c), .filter(|m| !common_modifiers.contains(m))
Key::AltF(n) => format!("{}", Key::F(*n)), .map(|m| m.to_string())
_ => format!("{}", key), .collect::<Vec<_>>()
.join(" ");
if key_modifier_for_key.is_empty() {
format!("{}", key.bare_key)
} else {
format!("{} {}", key_modifier_for_key, key.bare_key)
} }
} }
}) })
@ -538,20 +542,24 @@ pub mod tests {
use super::*; use super::*;
use ansi_term::unstyle; use ansi_term::unstyle;
use ansi_term::ANSIStrings; use ansi_term::ANSIStrings;
use zellij_tile::prelude::CharOrArrow;
use zellij_tile::prelude::Direction;
fn big_keymap() -> Vec<(Key, Vec<Action>)> { fn big_keymap() -> Vec<(KeyWithModifier, Vec<Action>)> {
vec![ vec![
(Key::Char('a'), vec![Action::Quit]), (KeyWithModifier::new(BareKey::Char('a')), vec![Action::Quit]),
(Key::Ctrl('b'), vec![Action::ScrollUp]),
(Key::Ctrl('d'), vec![Action::ScrollDown]),
( (
Key::Alt(CharOrArrow::Char('c')), KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
vec![Action::ScrollUp],
),
(
KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(),
vec![Action::ScrollDown],
),
(
KeyWithModifier::new(BareKey::Char('c')).with_alt_modifier(),
vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)], vec![Action::ScrollDown, Action::SwitchToMode(InputMode::Normal)],
), ),
( (
Key::Char('1'), KeyWithModifier::new(BareKey::Char('1')),
vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)],
), ),
] ]
@ -559,94 +567,65 @@ pub mod tests {
#[test] #[test]
fn common_modifier_with_ctrl_keys() { fn common_modifier_with_ctrl_keys() {
let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')]; let keyvec = vec![
let ret = get_common_modifier(keyvec.iter().collect()); KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
assert_eq!(ret, Some("Ctrl".to_string())); KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(),
];
let ret = get_common_modifiers(keyvec.iter().collect());
assert_eq!(ret, vec![KeyModifier::Ctrl]);
} }
#[test] #[test]
fn common_modifier_with_alt_keys_chars() { fn common_modifier_with_alt_keys_chars() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Char('1')), KeyWithModifier::new(BareKey::Char('1')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('t')), KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('z')), KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(),
]; ];
let ret = get_common_modifier(keyvec.iter().collect()); let ret = get_common_modifiers(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string())); assert_eq!(ret, vec![KeyModifier::Alt]);
}
#[test]
fn common_modifier_with_alt_keys_arrows() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Direction(Direction::Right)),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string()));
}
#[test]
fn common_modifier_with_alt_keys_arrows_and_chars() {
let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)),
Key::Alt(CharOrArrow::Direction(Direction::Right)),
Key::Alt(CharOrArrow::Char('t')),
Key::Alt(CharOrArrow::Char('z')),
];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, Some("Alt".to_string()));
} }
#[test] #[test]
fn common_modifier_with_mixed_alt_ctrl_keys() { fn common_modifier_with_mixed_alt_ctrl_keys() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)), KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier(),
Key::Alt(CharOrArrow::Char('z')), KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(),
Key::Ctrl('a'), KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(),
Key::Ctrl('1'),
]; ];
let ret = get_common_modifier(keyvec.iter().collect()); let ret = get_common_modifiers(keyvec.iter().collect());
assert_eq!(ret, None); assert_eq!(ret, vec![]); // no common modifiers
} }
#[test] #[test]
fn common_modifier_with_any_keys() { fn common_modifier_with_any_keys() {
let keyvec = vec![Key::Backspace, Key::Char('f'), Key::Down]; let keyvec = vec![
let ret = get_common_modifier(keyvec.iter().collect()); KeyWithModifier::new(BareKey::Char('1')),
assert_eq!(ret, None); KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(),
} KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(),
];
#[test] let ret = get_common_modifiers(keyvec.iter().collect());
fn common_modifier_with_ctrl_and_normal_keys() { assert_eq!(ret, vec![]); // no common modifiers
let keyvec = vec![Key::Ctrl('a'), Key::Char('f'), Key::Down];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
}
#[test]
fn common_modifier_with_alt_and_normal_keys() {
let keyvec = vec![Key::Alt(CharOrArrow::Char('a')), Key::Char('f'), Key::Down];
let ret = get_common_modifier(keyvec.iter().collect());
assert_eq!(ret, None);
} }
#[test] #[test]
fn action_key_simple_pattern_match_exact() { fn action_key_simple_pattern_match_exact() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])]; let keymap = &[(KeyWithModifier::new(BareKey::Char('f')), vec![Action::Quit])];
let ret = action_key(keymap, &[Action::Quit]); let ret = action_key(keymap, &[Action::Quit]);
assert_eq!(ret, vec![Key::Char('f')]); assert_eq!(ret, vec![KeyWithModifier::new(BareKey::Char('f'))]);
} }
#[test] #[test]
fn action_key_simple_pattern_match_pattern_too_long() { fn action_key_simple_pattern_match_pattern_too_long() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])]; let keymap = &[(KeyWithModifier::new(BareKey::Char('f')), vec![Action::Quit])];
let ret = action_key(keymap, &[Action::Quit, Action::ScrollUp]); let ret = action_key(keymap, &[Action::Quit, Action::ScrollUp]);
assert_eq!(ret, Vec::new()); assert_eq!(ret, Vec::new());
} }
#[test] #[test]
fn action_key_simple_pattern_match_pattern_empty() { fn action_key_simple_pattern_match_pattern_empty() {
let keymap = &[(Key::Char('f'), vec![Action::Quit])]; let keymap = &[(KeyWithModifier::new(BareKey::Char('f')), vec![Action::Quit])];
let ret = action_key(keymap, &[]); let ret = action_key(keymap, &[]);
assert_eq!(ret, Vec::new()); assert_eq!(ret, Vec::new());
} }
@ -655,7 +634,10 @@ pub mod tests {
fn action_key_long_pattern_match_exact() { fn action_key_long_pattern_match_exact() {
let keymap = big_keymap(); let keymap = big_keymap();
let ret = action_key(&keymap, &[Action::ScrollDown, TO_NORMAL]); let ret = action_key(&keymap, &[Action::ScrollDown, TO_NORMAL]);
assert_eq!(ret, vec![Key::Alt(CharOrArrow::Char('c'))]); assert_eq!(
ret,
vec![KeyWithModifier::new(BareKey::Char('c')).with_alt_modifier()]
);
} }
#[test] #[test]
@ -669,7 +651,7 @@ pub mod tests {
fn action_key_group_single_pattern() { fn action_key_group_single_pattern() {
let keymap = big_keymap(); let keymap = big_keymap();
let ret = action_key_group(&keymap, &[&[Action::Quit]]); let ret = action_key_group(&keymap, &[&[Action::Quit]]);
assert_eq!(ret, vec![Key::Char('a')]); assert_eq!(ret, vec![KeyWithModifier::new(BareKey::Char('a'))]);
} }
#[test] #[test]
@ -677,7 +659,13 @@ pub mod tests {
let keymap = big_keymap(); let keymap = big_keymap();
let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]); let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]);
// Mind the order! // Mind the order!
assert_eq!(ret, vec![Key::Ctrl('d'), Key::Ctrl('b')]); assert_eq!(
ret,
vec![
KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier()
]
);
} }
fn get_palette() -> Palette { fn get_palette() -> Palette {
@ -686,7 +674,11 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_only_chars() { fn style_key_with_modifier_only_chars() {
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Char('a')),
KeyWithModifier::new(BareKey::Char('b')),
KeyWithModifier::new(BareKey::Char('c')),
];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = style_key_with_modifier(&keyvec, &palette, None);
@ -698,10 +690,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_special_group_hjkl() { fn style_key_with_modifier_special_group_hjkl() {
let keyvec = vec![ let keyvec = vec![
Key::Char('h'), KeyWithModifier::new(BareKey::Char('h')),
Key::Char('j'), KeyWithModifier::new(BareKey::Char('j')),
Key::Char('k'), KeyWithModifier::new(BareKey::Char('k')),
Key::Char('l'), KeyWithModifier::new(BareKey::Char('l')),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -711,30 +703,13 @@ pub mod tests {
assert_eq!(ret, "<hjkl>".to_string()) assert_eq!(ret, "<hjkl>".to_string())
} }
#[test]
fn style_key_with_modifier_special_group_hjkl_broken() {
// Sorted the wrong way
let keyvec = vec![
Key::Char('h'),
Key::Char('k'),
Key::Char('j'),
Key::Char('l'),
];
let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<h|k|j|l>".to_string())
}
#[test] #[test]
fn style_key_with_modifier_special_group_all_arrows() { fn style_key_with_modifier_special_group_all_arrows() {
let keyvec = vec![ let keyvec = vec![
Key::Char('←'), KeyWithModifier::new(BareKey::Left),
Key::Char('↓'), KeyWithModifier::new(BareKey::Down),
Key::Char('↑'), KeyWithModifier::new(BareKey::Up),
Key::Char('→'), KeyWithModifier::new(BareKey::Right),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -746,7 +721,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_special_group_left_right_arrows() { fn style_key_with_modifier_special_group_left_right_arrows() {
let keyvec = vec![Key::Char('←'), Key::Char('→')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Left),
KeyWithModifier::new(BareKey::Right),
];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = style_key_with_modifier(&keyvec, &palette, None);
@ -757,7 +735,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_special_group_down_up_arrows() { fn style_key_with_modifier_special_group_down_up_arrows() {
let keyvec = vec![Key::Char('↓'), Key::Char('↑')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Down),
KeyWithModifier::new(BareKey::Up),
];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = style_key_with_modifier(&keyvec, &palette, None);
@ -769,10 +750,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_common_ctrl_modifier_chars() { fn style_key_with_modifier_common_ctrl_modifier_chars() {
let keyvec = vec![ let keyvec = vec![
Key::Ctrl('a'), KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
Key::Ctrl('b'), KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
Key::Ctrl('c'), KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(),
Key::Ctrl('d'), KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -785,10 +766,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_common_alt_modifier_chars() { fn style_key_with_modifier_common_alt_modifier_chars() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Char('a')), KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('b')), KeyWithModifier::new(BareKey::Char('b')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('c')), KeyWithModifier::new(BareKey::Char('c')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('d')), KeyWithModifier::new(BareKey::Char('d')).with_alt_modifier(),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -801,10 +782,10 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() { fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Direction(Direction::Left)), KeyWithModifier::new(BareKey::Left).with_alt_modifier(),
Key::Alt(CharOrArrow::Direction(Direction::Down)), KeyWithModifier::new(BareKey::Down).with_alt_modifier(),
Key::Alt(CharOrArrow::Direction(Direction::Up)), KeyWithModifier::new(BareKey::Up).with_alt_modifier(),
Key::Alt(CharOrArrow::Direction(Direction::Right)), KeyWithModifier::new(BareKey::Right).with_alt_modifier(),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -817,32 +798,32 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_ctrl_alt_char_mixed() { fn style_key_with_modifier_ctrl_alt_char_mixed() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Char('a')), KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier(),
Key::Ctrl('b'), KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
Key::Char('c'), KeyWithModifier::new(BareKey::Char('c')),
]; ];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = style_key_with_modifier(&keyvec, &palette, None);
let ret = unstyle(&ANSIStrings(&ret)); let ret = unstyle(&ANSIStrings(&ret));
assert_eq!(ret, "<Alt+a|Ctrl+b|c>".to_string()) assert_eq!(ret, "<Alt a|Ctrl b|c>".to_string())
} }
#[test] #[test]
fn style_key_with_modifier_unprintables() { fn style_key_with_modifier_unprintables() {
let keyvec = vec![ let keyvec = vec![
Key::Backspace, KeyWithModifier::new(BareKey::Backspace),
Key::Char('\n'), KeyWithModifier::new(BareKey::Enter),
Key::Char(' '), KeyWithModifier::new(BareKey::Char(' ')),
Key::Char('\t'), KeyWithModifier::new(BareKey::Tab),
Key::PageDown, KeyWithModifier::new(BareKey::PageDown),
Key::Delete, KeyWithModifier::new(BareKey::Delete),
Key::Home, KeyWithModifier::new(BareKey::Home),
Key::End, KeyWithModifier::new(BareKey::End),
Key::Insert, KeyWithModifier::new(BareKey::Insert),
Key::BackTab, KeyWithModifier::new(BareKey::Tab),
Key::Esc, KeyWithModifier::new(BareKey::Esc),
]; ];
let palette = get_palette(); let palette = get_palette();
@ -857,7 +838,11 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() { fn style_key_with_modifier_unprintables_with_common_ctrl_modifier() {
let keyvec = vec![Key::Ctrl('\n'), Key::Ctrl(' '), Key::Ctrl('\t')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Enter).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Char(' ')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Tab).with_ctrl_modifier(),
];
let palette = get_palette(); let palette = get_palette();
let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = style_key_with_modifier(&keyvec, &palette, None);
@ -869,9 +854,9 @@ pub mod tests {
#[test] #[test]
fn style_key_with_modifier_unprintables_with_common_alt_modifier() { fn style_key_with_modifier_unprintables_with_common_alt_modifier() {
let keyvec = vec![ let keyvec = vec![
Key::Alt(CharOrArrow::Char('\n')), KeyWithModifier::new(BareKey::Enter).with_alt_modifier(),
Key::Alt(CharOrArrow::Char(' ')), KeyWithModifier::new(BareKey::Char(' ')).with_alt_modifier(),
Key::Alt(CharOrArrow::Char('\t')), KeyWithModifier::new(BareKey::Tab).with_alt_modifier(),
]; ];
let palette = get_palette(); let palette = get_palette();

View file

@ -15,7 +15,7 @@ use crate::{
fn full_length_shortcut( fn full_length_shortcut(
is_first_shortcut: bool, is_first_shortcut: bool,
key: Vec<Key>, key: Vec<KeyWithModifier>,
action: &str, action: &str,
palette: Palette, palette: Palette,
) -> LinePart { ) -> LinePart {
@ -59,7 +59,12 @@ fn locked_interface_indication(palette: Palette) -> LinePart {
} }
} }
fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec<Key>) -> LinePart { fn add_shortcut(
help: &ModeInfo,
linepart: &LinePart,
text: &str,
keys: Vec<KeyWithModifier>,
) -> LinePart {
let shortcut = if linepart.len == 0 { let shortcut = if linepart.len == 0 {
full_length_shortcut(true, keys, text, help.style.colors) full_length_shortcut(true, keys, text, help.style.colors)
} else { } else {
@ -106,7 +111,7 @@ fn full_shortcut_list_nonstandard_mode(help: &ModeInfo) -> LinePart {
// three times the length and all the keybinding vectors we generate become virtually unreadable // three times the length and all the keybinding vectors we generate become virtually unreadable
// for humans. // for humans.
#[rustfmt::skip] #[rustfmt::skip]
fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> { fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<KeyWithModifier>)> {
use Action as A; use Action as A;
use InputMode as IM; use InputMode as IM;
use Direction as Dir; use Direction as Dir;
@ -119,8 +124,8 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
// Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other // Find a keybinding to get back to "Normal" input mode. In this case we prefer '\n' over other
// choices. Do it here before we dedupe the keymap below! // choices. Do it here before we dedupe the keymap below!
let to_normal_keys = action_key(&old_keymap, &[TO_NORMAL]); let to_normal_keys = action_key(&old_keymap, &[TO_NORMAL]);
let to_normal_key = if to_normal_keys.contains(&Key::Char('\n')) { let to_normal_key = if to_normal_keys.contains(&KeyWithModifier::new(BareKey::Enter)) {
vec![Key::Char('\n')] vec![KeyWithModifier::new(BareKey::Enter)]
} else { } else {
// Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise. // Yield `vec![key]` if `to_normal_keys` has at least one key, or an empty vec otherwise.
to_normal_keys.into_iter().take(1).collect() to_normal_keys.into_iter().take(1).collect()
@ -164,11 +169,11 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec<Key>)> {
// RightArrow. // RightArrow.
// FIXME: So for lack of a better idea we just check this case manually here. // FIXME: So for lack of a better idea we just check this case manually here.
let old_keymap = mi.get_mode_keybinds(); let old_keymap = mi.get_mode_keybinds();
let focus_keys_full: Vec<Key> = action_key_group(&old_keymap, let focus_keys_full: Vec<KeyWithModifier> = action_key_group(&old_keymap,
&[&[A::GoToPreviousTab], &[A::GoToNextTab]]); &[&[A::GoToPreviousTab], &[A::GoToNextTab]]);
let focus_keys = if focus_keys_full.contains(&Key::Left) let focus_keys = if focus_keys_full.contains(&KeyWithModifier::new(BareKey::Left))
&& focus_keys_full.contains(&Key::Right) { && focus_keys_full.contains(&KeyWithModifier::new(BareKey::Right)) {
vec![Key::Left, Key::Right] vec![KeyWithModifier::new(BareKey::Left), KeyWithModifier::new(BareKey::Right)]
} else { } else {
action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]]) action_key_group(&km, &[&[A::GoToPreviousTab], &[A::GoToNextTab]])
}; };
@ -429,7 +434,7 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart {
"{}", "{}",
action_key(km, &[Action::SwitchToMode(InputMode::Pane)]) action_key(km, &[Action::SwitchToMode(InputMode::Pane)])
.first() .first()
.unwrap_or(&Key::Char('?')) .unwrap_or(&KeyWithModifier::new(BareKey::Char('?')))
); );
let plus = ", "; let plus = ", ";
let p_left_separator = "<"; let p_left_separator = "<";
@ -440,7 +445,7 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart {
&[Action::ToggleFloatingPanes, TO_NORMAL] &[Action::ToggleFloatingPanes, TO_NORMAL]
) )
.first() .first()
.unwrap_or(&Key::Char('?')) .unwrap_or(&KeyWithModifier::new(BareKey::Char('?')))
); );
let p_right_separator = "> "; let p_right_separator = "> ";
let to_hide = "to hide."; let to_hide = "to hide.";
@ -560,7 +565,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_key() { fn full_length_shortcut_with_key() {
let keyvec = vec![Key::Char('a')]; let keyvec = vec![KeyWithModifier::new(BareKey::Char('a'))];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -571,7 +576,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_key_first_element() { fn full_length_shortcut_with_key_first_element() {
let keyvec = vec![Key::Char('a')]; let keyvec = vec![KeyWithModifier::new(BareKey::Char('a'))];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(true, keyvec, "Foobar", palette); let ret = full_length_shortcut(true, keyvec, "Foobar", palette);
@ -594,7 +599,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_key_unprintable_1() { fn full_length_shortcut_with_key_unprintable_1() {
let keyvec = vec![Key::Char('\n')]; let keyvec = vec![KeyWithModifier::new(BareKey::Enter)];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -605,7 +610,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_key_unprintable_2() { fn full_length_shortcut_with_key_unprintable_2() {
let keyvec = vec![Key::Backspace]; let keyvec = vec![KeyWithModifier::new(BareKey::Backspace)];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -616,7 +621,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_ctrl_key() { fn full_length_shortcut_with_ctrl_key() {
let keyvec = vec![Key::Ctrl('a')]; let keyvec = vec![KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier()];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -627,7 +632,7 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_alt_key() { fn full_length_shortcut_with_alt_key() {
let keyvec = vec![Key::Alt(CharOrArrow::Char('a'))]; let keyvec = vec![KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier()];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -638,7 +643,11 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_homogenous_key_group() { fn full_length_shortcut_with_homogenous_key_group() {
let keyvec = vec![Key::Char('a'), Key::Char('b'), Key::Char('c')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Char('a')),
KeyWithModifier::new(BareKey::Char('b')),
KeyWithModifier::new(BareKey::Char('c')),
];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -649,18 +658,26 @@ mod tests {
#[test] #[test]
fn full_length_shortcut_with_heterogenous_key_group() { fn full_length_shortcut_with_heterogenous_key_group() {
let keyvec = vec![Key::Char('a'), Key::Ctrl('b'), Key::Char('\n')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Char('a')),
KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Enter),
];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
let ret = unstyle(ret); let ret = unstyle(ret);
assert_eq!(ret, " / <a|Ctrl+b|ENTER> Foobar"); assert_eq!(ret, " / <a|Ctrl b|ENTER> Foobar");
} }
#[test] #[test]
fn full_length_shortcut_with_key_group_shared_ctrl_modifier() { fn full_length_shortcut_with_key_group_shared_ctrl_modifier() {
let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')]; let keyvec = vec![
KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(),
];
let palette = get_palette(); let palette = get_palette();
let ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = full_length_shortcut(false, keyvec, "Foobar", palette);
@ -678,14 +695,32 @@ mod tests {
keybinds: vec![( keybinds: vec![(
InputMode::Pane, InputMode::Pane,
vec![ vec![
(Key::Left, vec![Action::MoveFocus(Direction::Left)]),
(Key::Down, vec![Action::MoveFocus(Direction::Down)]),
(Key::Up, vec![Action::MoveFocus(Direction::Up)]),
(Key::Right, vec![Action::MoveFocus(Direction::Right)]),
(Key::Char('n'), vec![Action::NewPane(None, None), TO_NORMAL]),
(Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]),
( (
Key::Char('f'), KeyWithModifier::new(BareKey::Left),
vec![Action::MoveFocus(Direction::Left)],
),
(
KeyWithModifier::new(BareKey::Down),
vec![Action::MoveFocus(Direction::Down)],
),
(
KeyWithModifier::new(BareKey::Up),
vec![Action::MoveFocus(Direction::Up)],
),
(
KeyWithModifier::new(BareKey::Right),
vec![Action::MoveFocus(Direction::Right)],
),
(
KeyWithModifier::new(BareKey::Char('n')),
vec![Action::NewPane(None, None), TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::Char('x')),
vec![Action::CloseFocus, TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::Char('f')),
vec![Action::ToggleFocusFullscreen, TO_NORMAL], vec![Action::ToggleFocusFullscreen, TO_NORMAL],
), ),
], ],
@ -710,14 +745,32 @@ mod tests {
keybinds: vec![( keybinds: vec![(
InputMode::Pane, InputMode::Pane,
vec![ vec![
(Key::Left, vec![Action::MoveFocus(Direction::Left)]),
(Key::Down, vec![Action::MoveFocus(Direction::Down)]),
(Key::Up, vec![Action::MoveFocus(Direction::Up)]),
(Key::Right, vec![Action::MoveFocus(Direction::Right)]),
(Key::Char('n'), vec![Action::NewPane(None, None), TO_NORMAL]),
(Key::Char('x'), vec![Action::CloseFocus, TO_NORMAL]),
( (
Key::Char('f'), KeyWithModifier::new(BareKey::Left),
vec![Action::MoveFocus(Direction::Left)],
),
(
KeyWithModifier::new(BareKey::Down),
vec![Action::MoveFocus(Direction::Down)],
),
(
KeyWithModifier::new(BareKey::Up),
vec![Action::MoveFocus(Direction::Up)],
),
(
KeyWithModifier::new(BareKey::Right),
vec![Action::MoveFocus(Direction::Right)],
),
(
KeyWithModifier::new(BareKey::Char('n')),
vec![Action::NewPane(None, None), TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::Char('x')),
vec![Action::CloseFocus, TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::Char('f')),
vec![Action::ToggleFocusFullscreen, TO_NORMAL], vec![Action::ToggleFocusFullscreen, TO_NORMAL],
), ),
], ],
@ -738,13 +791,34 @@ mod tests {
keybinds: vec![( keybinds: vec![(
InputMode::Pane, InputMode::Pane,
vec![ vec![
(Key::Ctrl('a'), vec![Action::MoveFocus(Direction::Left)]), (
(Key::Ctrl('\n'), vec![Action::MoveFocus(Direction::Down)]), KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(),
(Key::Ctrl('1'), vec![Action::MoveFocus(Direction::Up)]), vec![Action::MoveFocus(Direction::Left)],
(Key::Ctrl(' '), vec![Action::MoveFocus(Direction::Right)]), ),
(Key::Backspace, vec![Action::NewPane(None, None), TO_NORMAL]), (
(Key::Esc, vec![Action::CloseFocus, TO_NORMAL]), KeyWithModifier::new(BareKey::Enter).with_ctrl_modifier(),
(Key::End, vec![Action::ToggleFocusFullscreen, TO_NORMAL]), vec![Action::MoveFocus(Direction::Down)],
),
(
KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier(),
vec![Action::MoveFocus(Direction::Up)],
),
(
KeyWithModifier::new(BareKey::Char(' ')).with_ctrl_modifier(),
vec![Action::MoveFocus(Direction::Right)],
),
(
KeyWithModifier::new(BareKey::Backspace),
vec![Action::NewPane(None, None), TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::Esc),
vec![Action::CloseFocus, TO_NORMAL],
),
(
KeyWithModifier::new(BareKey::End),
vec![Action::ToggleFocusFullscreen, TO_NORMAL],
),
], ],
)], )],
..ModeInfo::default() ..ModeInfo::default()

View file

@ -76,10 +76,10 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups {
&[Action::Resize(Resize::Decrease, None)], &[Action::Resize(Resize::Decrease, None)],
], ],
); );
if resize_keys.contains(&Key::Alt(CharOrArrow::Char('='))) if resize_keys.contains(&KeyWithModifier::new(BareKey::Char('=')).with_alt_modifier())
&& resize_keys.contains(&Key::Alt(CharOrArrow::Char('+'))) && resize_keys.contains(&KeyWithModifier::new(BareKey::Char('+')).with_alt_modifier())
{ {
resize_keys.retain(|k| k != &Key::Alt(CharOrArrow::Char('='))); resize_keys.retain(|k| k != &KeyWithModifier::new(BareKey::Char('=')).with_alt_modifier())
} }
let resize = if resize_keys.is_empty() { let resize = if resize_keys.is_empty() {
vec![Style::new().bold().paint("UNBOUND")] vec![Style::new().bold().paint("UNBOUND")]

View file

@ -63,42 +63,47 @@ impl ZellijPlugin for State {
self.update_files(paths); self.update_files(paths);
should_render = true; should_render = true;
}, },
Event::Key(key) => match key { Event::Key(key) => match key.bare_key {
Key::Char(character) if character != '\n' => { BareKey::Char(character) if key.has_no_modifiers() => {
self.update_search_term(character); self.update_search_term(character);
should_render = true; should_render = true;
}, },
Key::Backspace => { BareKey::Backspace if key.has_no_modifiers() => {
self.handle_backspace(); self.handle_backspace();
should_render = true; should_render = true;
}, },
Key::Esc | Key::Ctrl('c') => { BareKey::Esc if key.has_no_modifiers() => {
self.clear_search_term_or_descend(); self.clear_search_term_or_descend();
should_render = true; should_render = true;
}, },
Key::Up => { BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.clear_search_term_or_descend();
},
BareKey::Up if key.has_no_modifiers() => {
self.move_selection_up(); self.move_selection_up();
should_render = true; should_render = true;
}, },
Key::Down => { BareKey::Down if key.has_no_modifiers() => {
self.move_selection_down(); self.move_selection_down();
should_render = true; should_render = true;
}, },
Key::Char('\n') if 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(); self.send_filepick_response();
}, },
Key::Char('\n') => { BareKey::Enter if key.has_no_modifiers() => {
self.open_selected_path(); self.open_selected_path();
}, },
Key::Right | Key::BackTab => { BareKey::Right | BareKey::Tab if key.has_no_modifiers() => {
self.traverse_dir(); self.traverse_dir();
should_render = true; should_render = true;
}, },
Key::Left => { BareKey::Left if key.has_no_modifiers() => {
self.descend_to_previous_path(); self.descend_to_previous_path();
should_render = true; should_render = true;
}, },
Key::Ctrl('e') => { BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
should_render = true; should_render = true;
self.toggle_hidden_files(); self.toggle_hidden_files();
refresh_directory(&self.file_list_view.path); refresh_directory(&self.file_list_view.path);

View file

@ -2226,6 +2226,7 @@ pub fn send_command_through_the_cli() {
// cursor does not appear in // cursor does not appear in
// suspend_start panes // suspend_start panes
{ {
std::thread::sleep(std::time::Duration::from_millis(100));
remote_terminal.send_key(&SPACE); // run script - here we use SPACE remote_terminal.send_key(&SPACE); // run script - here we use SPACE
// instead of the default ENTER because // instead of the default ENTER because
// sending ENTER over SSH can be a little // sending ENTER over SSH can be a little
@ -2243,6 +2244,7 @@ pub fn send_command_through_the_cli() {
if remote_terminal.snapshot_contains("<Ctrl-c>") if remote_terminal.snapshot_contains("<Ctrl-c>")
&& remote_terminal.cursor_position_is(76, 3) && remote_terminal.cursor_position_is(76, 3)
{ {
std::thread::sleep(std::time::Duration::from_millis(100));
remote_terminal.send_key(&SPACE); // re-run script - here we use SPACE remote_terminal.send_key(&SPACE); // re-run script - here we use SPACE
// instead of the default ENTER because // instead of the default ENTER because
// sending ENTER over SSH can be a little // sending ENTER over SSH can be a little

View file

@ -237,6 +237,7 @@ fn read_from_channel(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_output = TerminalPane::new( let mut terminal_output = TerminalPane::new(
0, 0,
pane_geom, pane_geom,
@ -253,6 +254,7 @@ fn read_from_channel(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
loop { loop {
if !should_keep_running.load(Ordering::SeqCst) { if !should_keep_running.load(Ordering::SeqCst) {

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1048 assertion_line: 1171
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4 
@ -26,4 +26,4 @@ expression: last_snapshot
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT 
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide. (FLOATING PANES VISIBLE): Press Ctrl p, <w> to hide.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1109 assertion_line: 1232
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4 
@ -26,4 +26,4 @@ expression: last_snapshot
│ ││ │ │ ││ │
└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT 
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide. (FLOATING PANES VISIBLE): Press Ctrl p, <w> to hide.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1011 assertion_line: 1266
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -25,5 +25,5 @@ expression: last_snapshot
│ │ │ │
│ │ │ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
<F1> LOCK  <F2> PANE  <F3> TAB  <F4> RESIZE  <F5> MOVE  <F6> SEARCH  <Alt+F7> SESSION  <Ctrl+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. Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size.

View file

@ -1,6 +1,6 @@
--- ---
source: src/tests/e2e/cases.rs source: src/tests/e2e/cases.rs
assertion_line: 1724 assertion_line: 1984
expression: last_snapshot expression: last_snapshot
--- ---
Zellij (e2e-test)  Tab #1  Zellij (e2e-test)  Tab #1 
@ -26,4 +26,4 @@ expression: last_snapshot
│ │ │ │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT  Ctrl + <g> LOCK  <p> PANE  <t> TAB  <n> RESIZE  <h> MOVE  <s> SEARCH  <o> SESSION  <q> QUIT 
(FLOATING PANES VISIBLE): Press Ctrl+p, <w> to hide. (FLOATING PANES VISIBLE): Press Ctrl p, <w> to hide.

View file

@ -5,7 +5,7 @@ use crate::{
}; };
use zellij_utils::{ use zellij_utils::{
channels::{Receiver, SenderWithContext, OPENCALLS}, channels::{Receiver, SenderWithContext, OPENCALLS},
data::{InputMode, Key}, data::{InputMode, KeyWithModifier},
errors::{ContextType, ErrorContext, FatalError}, errors::{ContextType, ErrorContext, FatalError},
input::{ input::{
actions::Action, actions::Action,
@ -96,7 +96,7 @@ impl InputHandler {
&raw_bytes, &raw_bytes,
Some((&self.config.keybinds, &self.mode)), Some((&self.config.keybinds, &self.mode)),
); );
self.handle_key(&key, raw_bytes); self.handle_key(&key, raw_bytes, false);
}, },
InputEvent::Mouse(mouse_event) => { InputEvent::Mouse(mouse_event) => {
let mouse_event = let mouse_event =
@ -106,15 +106,15 @@ impl InputHandler {
InputEvent::Paste(pasted_text) => { InputEvent::Paste(pasted_text) => {
if self.mode == InputMode::Normal || self.mode == InputMode::Locked { if self.mode == InputMode::Normal || self.mode == InputMode::Locked {
self.dispatch_action( self.dispatch_action(
Action::Write(bracketed_paste_start.clone()), Action::Write(None, bracketed_paste_start.clone(), false),
None, None,
); );
self.dispatch_action( self.dispatch_action(
Action::Write(pasted_text.as_bytes().to_vec()), Action::Write(None, pasted_text.as_bytes().to_vec(), false),
None, None,
); );
self.dispatch_action( self.dispatch_action(
Action::Write(bracketed_paste_end.clone()), Action::Write(None, bracketed_paste_end.clone(), false),
None, None,
); );
} }
@ -140,6 +140,12 @@ impl InputHandler {
_ => {}, _ => {},
} }
}, },
Ok((
InputInstruction::KeyWithModifierEvent(key_with_modifier, raw_bytes),
_error_context,
)) => {
self.handle_key(&key_with_modifier, raw_bytes, true);
},
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => { Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
self.mode = input_mode; self.mode = input_mode;
}, },
@ -168,11 +174,19 @@ impl InputHandler {
} }
} }
} }
fn handle_key(&mut self, key: &Key, raw_bytes: Vec<u8>) { fn handle_key(
&mut self,
key: &KeyWithModifier,
raw_bytes: Vec<u8>,
is_kitty_keyboard_protocol: bool,
) {
let keybinds = &self.config.keybinds; let keybinds = &self.config.keybinds;
for action in for action in keybinds.get_actions_for_key_in_mode_or_default_action(
keybinds.get_actions_for_key_in_mode_or_default_action(&self.mode, key, raw_bytes) &self.mode,
{ key,
raw_bytes,
is_kitty_keyboard_protocol,
) {
let should_exit = self.dispatch_action(action, None); let should_exit = self.dispatch_action(action, None);
if should_exit { if should_exit {
self.should_exit = true; self.should_exit = true;

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,7 @@ pub mod os_input_output;
pub mod cli_client; pub mod cli_client;
mod command_is_executing; mod command_is_executing;
mod input_handler; mod input_handler;
mod keyboard_parser;
pub mod old_config_converter; pub mod old_config_converter;
mod stdin_ansi_parser; mod stdin_ansi_parser;
mod stdin_handler; mod stdin_handler;
@ -24,7 +25,7 @@ use crate::{
use zellij_utils::{ use zellij_utils::{
channels::{self, ChannelWithContext, SenderWithContext}, channels::{self, ChannelWithContext, SenderWithContext},
consts::{set_permissions, ZELLIJ_SOCK_DIR}, consts::{set_permissions, ZELLIJ_SOCK_DIR},
data::{ClientId, ConnectToSession, InputMode, Style}, data::{ClientId, ConnectToSession, InputMode, KeyWithModifier, Style},
envs, envs,
errors::{ClientContext, ContextType, ErrorInstruction}, errors::{ClientContext, ContextType, ErrorInstruction},
input::{config::Config, options::Options}, input::{config::Config, options::Options},
@ -152,6 +153,7 @@ impl ClientInfo {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) enum InputInstruction { pub(crate) enum InputInstruction {
KeyEvent(InputEvent, Vec<u8>), KeyEvent(InputEvent, Vec<u8>),
KeyWithModifierEvent(KeyWithModifier, Vec<u8>),
SwitchToMode(InputMode), SwitchToMode(InputMode),
AnsiStdinInstructions(Vec<AnsiStdinInstruction>), AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
StartedParsing, StartedParsing,
@ -177,16 +179,21 @@ pub fn start_client(
} }
info!("Starting Zellij client!"); info!("Starting Zellij client!");
let explicitly_disable_kitty_keyboard_protocol = config_options
.support_kitty_keyboard_protocol
.map(|e| !e)
.unwrap_or(false);
let mut reconnect_to_session = None; let mut reconnect_to_session = None;
let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l"; let clear_client_terminal_attributes = "\u{1b}[?1l\u{1b}=\u{1b}[r\u{1b}[?1000l\u{1b}[?1002l\u{1b}[?1003l\u{1b}[?1005l\u{1b}[?1006l\u{1b}[?12l";
let take_snapshot = "\u{1b}[?1049h"; let take_snapshot = "\u{1b}[?1049h";
let bracketed_paste = "\u{1b}[?2004h"; let bracketed_paste = "\u{1b}[?2004h";
let enter_kitty_keyboard_mode = "\u{1b}[>1u";
os_input.unset_raw_mode(0).unwrap(); os_input.unset_raw_mode(0).unwrap();
if !is_a_reconnect { if !is_a_reconnect {
// we don't do this for a reconnect because our controlling terminal already has the // we don't do this for a reconnect because our controlling terminal already has the
// attributes we want from it, and some terminals don't treat these atomically (looking at // attributes we want from it, and some terminals don't treat these atomically (looking at
// your Windows Terminal...) // you Windows Terminal...)
let _ = os_input let _ = os_input
.get_stdout_writer() .get_stdout_writer()
.write(take_snapshot.as_bytes()) .write(take_snapshot.as_bytes())
@ -195,6 +202,12 @@ pub fn start_client(
.get_stdout_writer() .get_stdout_writer()
.write(clear_client_terminal_attributes.as_bytes()) .write(clear_client_terminal_attributes.as_bytes())
.unwrap(); .unwrap();
if !explicitly_disable_kitty_keyboard_protocol {
let _ = os_input
.get_stdout_writer()
.write(enter_kitty_keyboard_mode.as_bytes())
.unwrap();
}
} }
envs::set_zellij("0".to_string()); envs::set_zellij("0".to_string());
config.env.set_vars(); config.env.set_vars();
@ -299,7 +312,14 @@ pub fn start_client(
let os_input = os_input.clone(); let os_input = os_input.clone();
let send_input_instructions = send_input_instructions.clone(); let send_input_instructions = send_input_instructions.clone();
let stdin_ansi_parser = stdin_ansi_parser.clone(); let stdin_ansi_parser = stdin_ansi_parser.clone();
move || stdin_loop(os_input, send_input_instructions, stdin_ansi_parser) move || {
stdin_loop(
os_input,
send_input_instructions,
stdin_ansi_parser,
explicitly_disable_kitty_keyboard_protocol,
)
}
}); });
let _input_thread = thread::Builder::new() let _input_thread = thread::Builder::new()
@ -533,6 +553,11 @@ pub fn start_client(
info!("{}", exit_msg); info!("{}", exit_msg);
os_input.unset_raw_mode(0).unwrap(); os_input.unset_raw_mode(0).unwrap();
let mut stdout = os_input.get_stdout_writer(); let mut stdout = os_input.get_stdout_writer();
let exit_kitty_keyboard_mode = "\u{1b}[<1u";
if !explicitly_disable_kitty_keyboard_protocol {
let _ = stdout.write(exit_kitty_keyboard_mode.as_bytes()).unwrap();
stdout.flush().unwrap();
}
let _ = stdout.write(goodbye_message.as_bytes()).unwrap(); let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
stdout.flush().unwrap(); stdout.flush().unwrap();
} else { } else {

View file

@ -1,3 +1,4 @@
use crate::keyboard_parser::KittyKeyboardParser;
use crate::os_input_output::ClientOsApi; use crate::os_input_output::ClientOsApi;
use crate::stdin_ansi_parser::StdinAnsiParser; use crate::stdin_ansi_parser::StdinAnsiParser;
use crate::InputInstruction; use crate::InputInstruction;
@ -23,6 +24,7 @@ pub(crate) fn stdin_loop(
mut os_input: Box<dyn ClientOsApi>, mut os_input: Box<dyn ClientOsApi>,
send_input_instructions: SenderWithContext<InputInstruction>, send_input_instructions: SenderWithContext<InputInstruction>,
stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>, stdin_ansi_parser: Arc<Mutex<StdinAnsiParser>>,
explicitly_disable_kitty_keyboard_protocol: bool,
) { ) {
let mut holding_mouse = false; let mut holding_mouse = false;
let mut input_parser = InputParser::new(); let mut input_parser = InputParser::new();
@ -85,6 +87,24 @@ pub(crate) fn stdin_loop(
.write_cache(ansi_stdin_events.drain(..).collect()); .write_cache(ansi_stdin_events.drain(..).collect());
} }
current_buffer.append(&mut buf.to_vec()); current_buffer.append(&mut buf.to_vec());
if !explicitly_disable_kitty_keyboard_protocol {
// first we try to parse with the KittyKeyboardParser
// if we fail, we try to parse normally
match KittyKeyboardParser::new().parse(&buf) {
Some(key_with_modifier) => {
send_input_instructions
.send(InputInstruction::KeyWithModifierEvent(
key_with_modifier,
current_buffer.drain(..).collect(),
))
.unwrap();
continue;
},
None => {},
}
}
let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely let maybe_more = false; // read_from_stdin should (hopefully) always empty the STDIN buffer completely
let mut events = vec![]; let mut events = vec![];
input_parser.parse( input_parser.parse(

View file

@ -362,6 +362,9 @@ pub struct Grid {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: 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)] #[derive(Clone, Debug)]
@ -450,6 +453,7 @@ impl Grid {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
) -> Self { ) -> Self {
let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store); let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store);
// make sure this is initialized as it is used internally // make sure this is initialized as it is used internally
@ -505,6 +509,8 @@ impl Grid {
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
lock_renders: false, lock_renders: false,
supports_kitty_keyboard_protocol: false,
explicitly_disable_kitty_keyboard_protocol,
} }
} }
pub fn render_full_viewport(&mut self) { pub fn render_full_viewport(&mut self) {
@ -2543,6 +2549,7 @@ impl Perform for Grid {
&mut self.viewport, &mut self.viewport,
&mut self.cursor, &mut self.cursor,
&mut self.sixel_grid, &mut self.sixel_grid,
&mut self.supports_kitty_keyboard_protocol,
); );
} }
self.alternate_screen_state = None; self.alternate_screen_state = None;
@ -2636,6 +2643,10 @@ impl Perform for Grid {
&mut self.cursor, &mut self.cursor,
Cursor::new(0, 0, self.styled_underlines), Cursor::new(0, 0, self.styled_underlines),
); );
let current_supports_kitty_keyboard_protocol = std::mem::replace(
&mut self.supports_kitty_keyboard_protocol,
false,
);
let sixel_image_store = self.sixel_grid.sixel_image_store.clone(); let sixel_image_store = self.sixel_grid.sixel_image_store.clone();
let alternate_sixelgrid = std::mem::replace( let alternate_sixelgrid = std::mem::replace(
&mut self.sixel_grid, &mut self.sixel_grid,
@ -2646,6 +2657,7 @@ impl Perform for Grid {
current_viewport, current_viewport,
current_cursor, current_cursor,
alternate_sixelgrid, alternate_sixelgrid,
current_supports_kitty_keyboard_protocol,
)); ));
self.clear_viewport_before_rendering = true; self.clear_viewport_before_rendering = true;
self.scrollback_buffer_lines = self.scrollback_buffer_lines =
@ -2825,6 +2837,27 @@ impl Perform for Grid {
} }
} else if c == 's' { } else if c == 's' {
self.save_cursor_position(); self.save_cursor_position();
} else if c == 'u' && intermediates == &[b'>'] {
// Zellij only supports the first "progressive enhancement" layer of the kitty keyboard
// protocol
if !self.explicitly_disable_kitty_keyboard_protocol {
self.supports_kitty_keyboard_protocol = true;
}
} else if c == 'u' && intermediates == &[b'<'] {
// Zellij only supports the first "progressive enhancement" layer of the kitty keyboard
// protocol
if !self.explicitly_disable_kitty_keyboard_protocol {
self.supports_kitty_keyboard_protocol = false;
}
} else if c == 'u' && intermediates == &[b'?'] {
// Zellij only supports the first "progressive enhancement" layer of the kitty keyboard
// protocol
let reply = if self.supports_kitty_keyboard_protocol {
"\u{1b}[?1u"
} else {
"\u{1b}[?0u"
};
self.pending_messages_to_pty.push(reply.as_bytes().to_vec());
} else if c == 'u' { } else if c == 'u' {
self.restore_cursor_position(); self.restore_cursor_position();
} else if c == '@' { } else if c == '@' {
@ -3084,6 +3117,7 @@ pub struct AlternateScreenState {
viewport: Vec<Row>, viewport: Vec<Row>,
cursor: Cursor, cursor: Cursor,
sixel_grid: SixelGrid, sixel_grid: SixelGrid,
supports_kitty_keyboard_protocol: bool,
} }
impl AlternateScreenState { impl AlternateScreenState {
pub fn new( pub fn new(
@ -3091,12 +3125,14 @@ impl AlternateScreenState {
viewport: Vec<Row>, viewport: Vec<Row>,
cursor: Cursor, cursor: Cursor,
sixel_grid: SixelGrid, sixel_grid: SixelGrid,
supports_kitty_keyboard_protocol: bool,
) -> Self { ) -> Self {
AlternateScreenState { AlternateScreenState {
lines_above, lines_above,
viewport, viewport,
cursor, cursor,
sixel_grid, sixel_grid,
supports_kitty_keyboard_protocol,
} }
} }
pub fn apply_contents_to( pub fn apply_contents_to(
@ -3105,11 +3141,16 @@ impl AlternateScreenState {
viewport: &mut Vec<Row>, viewport: &mut Vec<Row>,
cursor: &mut Cursor, cursor: &mut Cursor,
sixel_grid: &mut SixelGrid, sixel_grid: &mut SixelGrid,
supports_kitty_keyboard_protocol: &mut bool,
) { ) {
std::mem::swap(&mut self.lines_above, lines_above); std::mem::swap(&mut self.lines_above, lines_above);
std::mem::swap(&mut self.viewport, viewport); std::mem::swap(&mut self.viewport, viewport);
std::mem::swap(&mut self.cursor, cursor); std::mem::swap(&mut self.cursor, cursor);
std::mem::swap(&mut self.sixel_grid, sixel_grid); std::mem::swap(&mut self.sixel_grid, sixel_grid);
std::mem::swap(
&mut self.supports_kitty_keyboard_protocol,
supports_kitty_keyboard_protocol,
);
} }
} }

View file

@ -2,7 +2,12 @@ use std::collections::{BTreeSet, HashMap};
use std::time::Instant; use std::time::Instant;
use crate::output::{CharacterChunk, SixelImageChunk}; use crate::output::{CharacterChunk, SixelImageChunk};
use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId}; use crate::panes::{
grid::Grid,
sixel::SixelImageStore,
terminal_pane::{BRACKETED_PASTE_BEGIN, BRACKETED_PASTE_END},
LinkHandler, PaneId,
};
use crate::plugins::PluginInstruction; use crate::plugins::PluginInstruction;
use crate::pty::VteBytes; use crate::pty::VteBytes;
use crate::tab::{AdjustedInput, Pane}; use crate::tab::{AdjustedInput, Pane};
@ -13,7 +18,9 @@ use crate::ui::{
use crate::ClientId; use crate::ClientId;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use zellij_utils::data::{PermissionStatus, PermissionType, PluginPermission}; use zellij_utils::data::{
BareKey, KeyWithModifier, PermissionStatus, PermissionType, PluginPermission,
};
use zellij_utils::pane_size::{Offset, SizeInPixels}; use zellij_utils::pane_size::{Offset, SizeInPixels};
use zellij_utils::position::Position; use zellij_utils::position::Position;
use zellij_utils::{ use zellij_utils::{
@ -39,6 +46,7 @@ macro_rules! get_or_create_grid {
($self:ident, $client_id:ident) => {{ ($self:ident, $client_id:ident) => {{
let rows = $self.get_content_rows(); let rows = $self.get_content_rows();
let cols = $self.get_content_columns(); let cols = $self.get_content_columns();
let explicitly_disable_kitty_keyboard_protocol = false; // N/A for plugins
$self.grids.entry($client_id).or_insert_with(|| { $self.grids.entry($client_id).or_insert_with(|| {
let mut grid = Grid::new( let mut grid = Grid::new(
@ -53,6 +61,7 @@ macro_rules! get_or_create_grid {
$self.debug, $self.debug,
$self.arrow_fonts, $self.arrow_fonts,
$self.styled_underlines, $self.styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
grid.hide_cursor(); grid.hide_cursor();
grid grid
@ -232,24 +241,54 @@ impl Pane for PluginPane {
fn cursor_coordinates(&self) -> Option<(usize, usize)> { fn cursor_coordinates(&self) -> Option<(usize, usize)> {
None None
} }
fn adjust_input_to_terminal(&mut self, input_bytes: Vec<u8>) -> 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 { if let Some(requesting_permissions) = &self.requesting_permissions {
let permissions = requesting_permissions.permissions.clone(); let permissions = requesting_permissions.permissions.clone();
match input_bytes.as_slice() { if let Some(key_with_modifier) = key_with_modifier {
// Y or y match key_with_modifier.bare_key {
&[89] | &[121] => Some(AdjustedInput::PermissionRequestResult( BareKey::Char('y') if key_with_modifier.has_no_modifiers() => {
permissions, Some(AdjustedInput::PermissionRequestResult(
PermissionStatus::Granted, permissions,
)), PermissionStatus::Granted,
// N or n ))
&[78] | &[110] => Some(AdjustedInput::PermissionRequestResult( },
permissions, BareKey::Char('n') if key_with_modifier.has_no_modifiers() => {
PermissionStatus::Denied, Some(AdjustedInput::PermissionRequestResult(
)), permissions,
_ => None, 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 { } else {
Some(AdjustedInput::WriteBytesToTerminal(input_bytes)) Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
} }
} }
fn position_and_size(&self) -> PaneGeom { fn position_and_size(&self) -> PaneGeom {

View file

@ -16,7 +16,10 @@ use std::time::{self, Instant};
use zellij_utils::input::command::RunCommand; use zellij_utils::input::command::RunCommand;
use zellij_utils::pane_size::Offset; use zellij_utils::pane_size::Offset;
use zellij_utils::{ use zellij_utils::{
data::{InputMode, Palette, PaletteColor, PaneId as ZellijUtilsPaneId, Style}, data::{
BareKey, InputMode, KeyWithModifier, Palette, PaletteColor, PaneId as ZellijUtilsPaneId,
Style,
},
errors::prelude::*, errors::prelude::*,
input::layout::Run, input::layout::Run,
pane_size::PaneGeom, pane_size::PaneGeom,
@ -37,8 +40,8 @@ const UP_ARROW: &[u8] = &[27, 91, 65];
const DOWN_ARROW: &[u8] = &[27, 91, 66]; const DOWN_ARROW: &[u8] = &[27, 91, 66];
const HOME_KEY: &[u8] = &[27, 91, 72]; const HOME_KEY: &[u8] = &[27, 91, 72];
const END_KEY: &[u8] = &[27, 91, 70]; const END_KEY: &[u8] = &[27, 91, 70];
const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126]; pub 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_END: &[u8] = &[27, 91, 50, 48, 49, 126];
const ENTER_NEWLINE: &[u8] = &[10]; const ENTER_NEWLINE: &[u8] = &[10];
const ESC: &[u8] = &[27]; const ESC: &[u8] = &[27];
const ENTER_CARRIAGE_RETURN: &[u8] = &[13]; const ENTER_CARRIAGE_RETURN: &[u8] = &[13];
@ -190,92 +193,71 @@ impl Pane for TerminalPane {
.cursor_coordinates() .cursor_coordinates()
.map(|(x, y)| (x + left, y + top)) .map(|(x, y)| (x + left, y + top))
} }
fn adjust_input_to_terminal(&mut self, input_bytes: Vec<u8>) -> 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> {
// there are some cases in which the terminal state means that input sent to it // there are some cases in which the terminal state means that input sent to it
// needs to be adjusted. // needs to be adjusted.
// here we match against those cases - if need be, we adjust the input and if not // here we match against those cases - if need be, we adjust the input and if not
// we send back the original input // we send back the original input
if let Some((_exit_status, _is_first_run, run_command)) = &self.is_held {
match input_bytes.as_slice() {
ENTER_CARRIAGE_RETURN | ENTER_NEWLINE | SPACE => {
let run_command = run_command.clone();
self.is_held = None;
self.grid.reset_terminal_state();
self.set_should_render(true);
self.remove_banner();
Some(AdjustedInput::ReRunCommandInThisPane(run_command))
},
ESC => {
// Drop to shell in the same working directory as the command was run
let working_dir = run_command.cwd.clone();
self.is_held = None;
self.grid.reset_terminal_state();
self.set_should_render(true);
self.remove_banner();
Some(AdjustedInput::DropToShellInThisPane { working_dir })
},
CTRL_C => Some(AdjustedInput::CloseThisPane),
_ => None,
}
} else {
if self.grid.new_line_mode {
if let &[13] = input_bytes.as_slice() {
// LNM - carriage return is followed by linefeed
return Some(AdjustedInput::WriteBytesToTerminal(
"\u{0d}\u{0a}".as_bytes().to_vec(),
));
};
}
if self.grid.cursor_key_mode {
match input_bytes.as_slice() {
LEFT_ARROW => {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Left.as_vec_bytes(),
));
},
RIGHT_ARROW => {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Right.as_vec_bytes(),
));
},
UP_ARROW => {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Up.as_vec_bytes(),
));
},
DOWN_ARROW => {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Down.as_vec_bytes(),
));
},
HOME_KEY => { if !self.grid.bracketed_paste_mode {
return Some(AdjustedInput::WriteBytesToTerminal( // Zellij itself operates in bracketed paste mode, so the terminal sends these
AnsiEncoding::Home.as_vec_bytes(), // instructions (bracketed paste start and bracketed paste end respectively)
)); // when pasting input. We only need to make sure not to send them to terminal
}, // panes who do not work in this mode
END_KEY => { match raw_input_bytes.as_slice() {
return Some(AdjustedInput::WriteBytesToTerminal( BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => {
AnsiEncoding::End.as_vec_bytes(), return Some(AdjustedInput::WriteBytesToTerminal(vec![]))
)); },
}, _ => {},
_ => {},
};
} }
}
if !self.grid.bracketed_paste_mode { if self.is_held.is_some() {
// Zellij itself operates in bracketed paste mode, so the terminal sends these if key_with_modifier
// instructions (bracketed paste start and bracketed paste end respectively) .as_ref()
// when pasting input. We only need to make sure not to send them to terminal .map(|k| k.is_key_without_modifier(BareKey::Enter))
// panes who do not work in this mode .unwrap_or(false)
match input_bytes.as_slice() { {
BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => { self.handle_held_run()
return Some(AdjustedInput::WriteBytesToTerminal(vec![])) } else if key_with_modifier
}, .as_ref()
_ => {}, .map(|k| k.is_key_without_modifier(BareKey::Esc))
.unwrap_or(false)
{
self.handle_held_drop_to_shell()
} else if key_with_modifier
.as_ref()
.map(|k| k.is_key_with_ctrl_modifier(BareKey::Char('c')))
.unwrap_or(false)
{
Some(AdjustedInput::CloseThisPane)
} else {
match raw_input_bytes.as_slice() {
ENTER_CARRIAGE_RETURN | ENTER_NEWLINE | SPACE => self.handle_held_run(),
ESC => self.handle_held_drop_to_shell(),
CTRL_C => Some(AdjustedInput::CloseThisPane),
_ => None,
} }
} }
Some(AdjustedInput::WriteBytesToTerminal(input_bytes)) } else {
if self.grid.supports_kitty_keyboard_protocol {
self.adjust_input_to_terminal_with_kitty_keyboard_protocol(
key_with_modifier,
raw_input_bytes,
raw_input_bytes_are_kitty,
)
} else {
self.adjust_input_to_terminal_without_kitty_keyboard_protocol(
key_with_modifier,
raw_input_bytes,
raw_input_bytes_are_kitty,
)
}
} }
} }
fn position_and_size(&self) -> PaneGeom { fn position_and_size(&self) -> PaneGeom {
@ -804,6 +786,7 @@ impl TerminalPane {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_keyboard_protocol: bool,
) -> TerminalPane { ) -> TerminalPane {
let initial_pane_title = let initial_pane_title =
initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index)); initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index));
@ -819,6 +802,7 @@ impl TerminalPane {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_keyboard_protocol,
); );
TerminalPane { TerminalPane {
frame: HashMap::new(), frame: HashMap::new(),
@ -910,6 +894,131 @@ impl TerminalPane {
self.banner = None; self.banner = None;
} }
} }
fn adjust_input_to_terminal_with_kitty_keyboard_protocol(
&self,
key: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
raw_input_bytes_are_kitty: bool,
) -> Option<AdjustedInput> {
if raw_input_bytes_are_kitty {
Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
} else {
// here what happens is that the host terminal is operating in non "kitty keys" mode, but
// this terminal pane *is* operating in "kitty keys" mode - so we need to serialize the "non kitty"
// key to a "kitty key"
key.as_ref()
.and_then(|k| k.serialize_kitty())
.map(|s| AdjustedInput::WriteBytesToTerminal(s.as_bytes().to_vec()))
}
}
fn adjust_input_to_terminal_without_kitty_keyboard_protocol(
&self,
key: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
raw_input_bytes_are_kitty: bool,
) -> Option<AdjustedInput> {
if self.grid.new_line_mode {
let key_is_enter = raw_input_bytes.as_slice() == &[13]
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Enter))
.unwrap_or(false);
if key_is_enter {
// LNM - carriage return is followed by linefeed
return Some(AdjustedInput::WriteBytesToTerminal(
"\u{0d}\u{0a}".as_bytes().to_vec(),
));
};
}
if self.grid.cursor_key_mode {
let key_is_left_arrow = raw_input_bytes.as_slice() == LEFT_ARROW
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Left))
.unwrap_or(false);
let key_is_right_arrow = raw_input_bytes.as_slice() == RIGHT_ARROW
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Right))
.unwrap_or(false);
let key_is_up_arrow = raw_input_bytes.as_slice() == UP_ARROW
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Up))
.unwrap_or(false);
let key_is_down_arrow = raw_input_bytes.as_slice() == DOWN_ARROW
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Down))
.unwrap_or(false);
let key_is_home_key = raw_input_bytes.as_slice() == HOME_KEY
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::Home))
.unwrap_or(false);
let key_is_end_key = raw_input_bytes.as_slice() == END_KEY
|| key
.as_ref()
.map(|k| k.is_key_without_modifier(BareKey::End))
.unwrap_or(false);
if key_is_left_arrow {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Left.as_vec_bytes(),
));
} else if key_is_right_arrow {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Right.as_vec_bytes(),
));
} else if key_is_up_arrow {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Up.as_vec_bytes(),
));
} else if key_is_down_arrow {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Down.as_vec_bytes(),
));
} else if key_is_home_key {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::Home.as_vec_bytes(),
));
} else if key_is_end_key {
return Some(AdjustedInput::WriteBytesToTerminal(
AnsiEncoding::End.as_vec_bytes(),
));
}
}
if raw_input_bytes_are_kitty {
// here what happens is that the host terminal is operating in "kitty keys" mode, but
// this terminal pane is not - so we need to serialize the kitty key to "non kitty" if
// possible - if not possible (eg. with multiple modifiers), we'll return a None here
// and write nothing to the terminal pane
key.as_ref()
.and_then(|k| k.serialize_non_kitty())
.map(|s| AdjustedInput::WriteBytesToTerminal(s.as_bytes().to_vec()))
} else {
Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes))
}
}
fn handle_held_run(&mut self) -> Option<AdjustedInput> {
self.is_held.take().map(|(_, _, run_command)| {
self.is_held = None;
self.grid.reset_terminal_state();
self.set_should_render(true);
self.remove_banner();
AdjustedInput::ReRunCommandInThisPane(run_command.clone())
})
}
fn handle_held_drop_to_shell(&mut self) -> Option<AdjustedInput> {
self.is_held.take().map(|(_, _, run_command)| {
// Drop to shell in the same working directory as the command was run
let working_dir = run_command.cwd.clone();
self.is_held = None;
self.grid.reset_terminal_state();
self.set_should_render(true);
self.remove_banner();
AdjustedInput::DropToShellInThisPane { working_dir }
})
}
} }
#[cfg(test)] #[cfg(test)]

File diff suppressed because it is too large Load diff

View file

@ -31,6 +31,7 @@ fn create_pane() -> TerminalPane {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -47,6 +48,7 @@ fn create_pane() -> TerminalPane {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let content = read_fixture(); let content = read_fixture();
terminal_pane.handle_pty_bytes(content); terminal_pane.handle_pty_bytes(content);

View file

@ -39,6 +39,7 @@ pub fn scrolling_inside_a_pane() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -55,6 +56,7 @@ pub fn scrolling_inside_a_pane() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -87,6 +89,7 @@ pub fn sixel_image_inside_terminal_pane() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -103,6 +106,7 @@ pub fn sixel_image_inside_terminal_pane() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}Pq let sixel_image_bytes = "\u{1b}Pq
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0 #0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
@ -135,6 +139,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -151,6 +156,7 @@ pub fn partial_sixel_image_inside_terminal_pane() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -177,6 +183,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -193,6 +200,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let pane_content = read_fixture("sixel-image-500px.six"); let pane_content = read_fixture("sixel-image-500px.six");
terminal_pane.handle_pty_bytes(pane_content); terminal_pane.handle_pty_bytes(pane_content);
@ -218,6 +226,7 @@ pub fn scrolling_through_a_sixel_image() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -234,6 +243,7 @@ pub fn scrolling_through_a_sixel_image() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..30 { for i in 0..30 {
@ -270,6 +280,7 @@ pub fn multiple_sixel_images_in_pane() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -286,6 +297,7 @@ pub fn multiple_sixel_images_in_pane() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -320,6 +332,7 @@ pub fn resizing_pane_with_sixel_images() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -336,6 +349,7 @@ pub fn resizing_pane_with_sixel_images() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -373,6 +387,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -389,6 +404,7 @@ pub fn changing_character_cell_size_with_sixel_images() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let mut text_to_fill_pane = String::new(); let mut text_to_fill_pane = String::new();
for i in 0..5 { for i in 0..5 {
@ -431,6 +447,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -447,6 +464,7 @@ pub fn keep_working_after_corrupted_sixel_image() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq let sixel_image_bytes = "\u{1b}PI AM CORRUPTED BWAHAHAq
@ -487,6 +505,7 @@ pub fn pane_with_frame_position_is_on_frame() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -503,6 +522,7 @@ pub fn pane_with_frame_position_is_on_frame() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::frame(1)); terminal_pane.set_content_offset(Offset::frame(1));
@ -579,6 +599,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -595,6 +616,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::shift(1, 1)); terminal_pane.set_content_offset(Offset::shift(1, 1));
@ -671,6 +693,7 @@ pub fn frameless_pane_position_is_on_frame() {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut terminal_pane = TerminalPane::new( let mut terminal_pane = TerminalPane::new(
pid, pid,
fake_win_size, fake_win_size,
@ -687,6 +710,7 @@ pub fn frameless_pane_position_is_on_frame() {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); // 0 is the pane index ); // 0 is the pane index
terminal_pane.set_content_offset(Offset::default()); terminal_pane.set_content_offset(Offset::default());

View file

@ -6,7 +6,9 @@ use std::collections::BTreeMap;
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::tempdir; use tempfile::tempdir;
use wasmer::Store; use wasmer::Store;
use zellij_utils::data::{Event, Key, PermissionStatus, PermissionType, PluginCapabilities}; use zellij_utils::data::{
BareKey, Event, KeyWithModifier, PermissionStatus, PermissionType, PluginCapabilities,
};
use zellij_utils::errors::ErrorContext; use zellij_utils::errors::ErrorContext;
use zellij_utils::input::layout::{ use zellij_utils::input::layout::{
Layout, PluginAlias, PluginUserConfiguration, RunPlugin, RunPluginLocation, RunPluginOrAlias, Layout, PluginAlias, PluginUserConfiguration, RunPlugin, RunPluginLocation, RunPluginOrAlias,
@ -1016,8 +1018,8 @@ pub fn switch_to_mode_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('a')), // this triggers a SwitchToMode(Tab) command in the fixture Event::Key(KeyWithModifier::new(BareKey::Char('a'))), // this triggers a SwitchToMode(Tab) command in the fixture
// plugin // plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1088,8 +1090,8 @@ pub fn switch_to_mode_plugin_command_permission_denied() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('a')), // this triggers a SwitchToMode(Tab) command in the fixture Event::Key(KeyWithModifier::new(BareKey::Char('a'))), // this triggers a SwitchToMode(Tab) command in the fixture
// plugin // plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1160,8 +1162,8 @@ pub fn new_tabs_with_layout_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('b')), // this triggers a new_tabs_with_layout command in the fixture Event::Key(KeyWithModifier::new(BareKey::Char('b'))), // this triggers a new_tabs_with_layout command in the fixture
// plugin // plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1246,8 +1248,8 @@ pub fn new_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('c')), // this triggers a new_tab command in the fixture Event::Key(KeyWithModifier::new(BareKey::Char('c'))), // this triggers a new_tab command in the fixture
// plugin // plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1318,7 +1320,7 @@ pub fn go_to_next_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('d')), // this triggers the event in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('d'))), // this triggers the event in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1389,7 +1391,7 @@ pub fn go_to_previous_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('e')), // this triggers the event in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('e'))), // this triggers the event in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1460,7 +1462,7 @@ pub fn resize_focused_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('f')), // this triggers the event in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('f'))), // this triggers the event in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1531,7 +1533,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('g')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('g'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1602,7 +1604,7 @@ pub fn focus_next_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('h')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('h'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1673,7 +1675,7 @@ pub fn focus_previous_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('i')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('i'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1744,7 +1746,7 @@ pub fn move_focus_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('j')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('j'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1815,7 +1817,7 @@ pub fn move_focus_or_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('k')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('k'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1886,7 +1888,7 @@ pub fn edit_scrollback_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('m')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('m'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -1957,7 +1959,7 @@ pub fn write_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('n')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('n'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2028,7 +2030,7 @@ pub fn write_chars_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('o')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('o'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2099,7 +2101,7 @@ pub fn toggle_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('p')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('p'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2170,7 +2172,7 @@ pub fn move_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('q')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('q'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2241,7 +2243,7 @@ pub fn move_pane_with_direction_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('r')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('r'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2313,7 +2315,7 @@ pub fn clear_screen_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('s')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('s'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2385,7 +2387,7 @@ pub fn scroll_up_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('t')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('t'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2456,7 +2458,7 @@ pub fn scroll_down_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('u')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('u'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2527,7 +2529,7 @@ pub fn scroll_to_top_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('v')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('v'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2598,7 +2600,7 @@ pub fn scroll_to_bottom_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('w')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('w'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2669,7 +2671,7 @@ pub fn page_scroll_up_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('x')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('x'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2740,7 +2742,7 @@ pub fn page_scroll_down_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('y')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('y'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2811,7 +2813,7 @@ pub fn toggle_focus_fullscreen_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('z')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('z'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2882,7 +2884,7 @@ pub fn toggle_pane_frames_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('1')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('1'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -2953,7 +2955,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('2')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('2'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3024,7 +3026,7 @@ pub fn undo_rename_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('3')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('3'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3095,7 +3097,7 @@ pub fn close_focus_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('4')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('4'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3166,7 +3168,7 @@ pub fn toggle_active_tab_sync_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('5')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('5'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3237,7 +3239,7 @@ pub fn close_focused_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('6')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('6'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3308,7 +3310,7 @@ pub fn undo_rename_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('7')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('7'))), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3379,7 +3381,7 @@ pub fn previous_swap_layout_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('a')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3450,7 +3452,7 @@ pub fn next_swap_layout_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('b')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3521,7 +3523,7 @@ pub fn go_to_tab_name_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('c')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3592,7 +3594,7 @@ pub fn focus_or_create_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('d')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3663,7 +3665,7 @@ pub fn go_to_tab() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('e')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('e')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3734,7 +3736,7 @@ pub fn start_or_reload_plugin() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('f')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('f')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3812,7 +3814,7 @@ pub fn quit_zellij_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('8')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('8'))), // this triggers the enent in the fixture plugin
)])); )]));
server_thread.join().unwrap(); // this might take a while if the cache is cold server_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3890,7 +3892,7 @@ pub fn detach_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('l')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('l'))), // this triggers the enent in the fixture plugin
)])); )]));
server_thread.join().unwrap(); // this might take a while if the cache is cold server_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -3968,7 +3970,7 @@ pub fn open_file_floating_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('h')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('h')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4050,7 +4052,7 @@ pub fn open_file_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('g')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4133,7 +4135,7 @@ pub fn open_file_with_line_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('i')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('i')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4215,7 +4217,7 @@ pub fn open_file_with_line_floating_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('j')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('j')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4297,7 +4299,7 @@ pub fn open_terminal_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('k')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('k')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4375,7 +4377,7 @@ pub fn open_terminal_floating_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('l')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('l')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4453,7 +4455,7 @@ pub fn open_command_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('m')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('m')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4531,7 +4533,7 @@ pub fn open_command_pane_floating_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('n')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('n')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
pty_thread.join().unwrap(); // this might take a while if the cache is cold pty_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4602,7 +4604,7 @@ pub fn switch_to_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('o')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('o')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4673,7 +4675,7 @@ pub fn hide_self_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('p')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('p')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4743,7 +4745,7 @@ pub fn show_self_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('q')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('q')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4814,7 +4816,7 @@ pub fn close_terminal_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('r')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('r')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4885,7 +4887,7 @@ pub fn close_plugin_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('s')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('s')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -4956,7 +4958,7 @@ pub fn focus_terminal_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('t')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('t')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5027,7 +5029,7 @@ pub fn focus_plugin_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('u')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('u')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5098,7 +5100,7 @@ pub fn rename_terminal_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('v')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('v')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5169,7 +5171,7 @@ pub fn rename_plugin_pane_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('w')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('w')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5240,7 +5242,7 @@ pub fn rename_tab_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('x')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('x')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5320,7 +5322,7 @@ pub fn send_configuration_to_plugins() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('z')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('z')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5388,7 +5390,7 @@ pub fn request_plugin_permissions() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('1')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); // this might take a while if the cache is cold screen_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5480,7 +5482,7 @@ pub fn granted_permission_request_result() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('1')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); screen_thread.join().unwrap();
teardown(); teardown();
@ -5571,7 +5573,7 @@ pub fn denied_permission_request_result() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('1')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
screen_thread.join().unwrap(); screen_thread.join().unwrap();
teardown(); teardown();
@ -5642,7 +5644,7 @@ pub fn run_command_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('2')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('2')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5720,7 +5722,7 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('3')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('3')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -5798,7 +5800,7 @@ pub fn web_request_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('4')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('4')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold background_jobs_thread.join().unwrap(); // this might take a while if the cache is cold
teardown(); teardown();
@ -6219,7 +6221,7 @@ pub fn switch_session_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('5')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('5')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
teardown(); teardown();
@ -6300,7 +6302,7 @@ pub fn switch_session_with_layout_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('7')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('7')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
teardown(); teardown();
@ -6381,7 +6383,7 @@ pub fn switch_session_with_layout_and_cwd_plugin_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('9')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('9')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
teardown(); teardown();
@ -6462,7 +6464,7 @@ pub fn disconnect_other_clients_plugins_command() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('6')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('6')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
teardown(); teardown();
@ -6547,13 +6549,13 @@ pub fn run_plugin_in_specific_cwd() {
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Ctrl('8')), // this triggers the enent in the fixture plugin Event::Key(KeyWithModifier::new(BareKey::Char('8')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
)])); )]));
std::thread::sleep(std::time::Duration::from_millis(500)); std::thread::sleep(std::time::Duration::from_millis(500));
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
None, None,
Some(client_id), Some(client_id),
Event::Key(Key::Char('8')), // this sends this quit command so tha the test exits cleanly Event::Key(KeyWithModifier::new(BareKey::Char('8'))), // this sends this quit command so tha the test exits cleanly
)])); )]));
teardown(); teardown();
server_thread.join().unwrap(); // this might take a while if the cache is cold server_thread.join().unwrap(); // this might take a while if the cache is cold

View file

@ -1,15 +1,17 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 1449 assertion_line: 2047
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
WriteCharacter( WriteCharacter(
None,
[ [
102, 102,
111, 111,
111, 111,
], ],
false,
1, 1,
), ),
) )

View file

@ -1,15 +1,17 @@
--- ---
source: zellij-server/src/plugins/./unit/plugin_tests.rs source: zellij-server/src/plugins/./unit/plugin_tests.rs
assertion_line: 1117 assertion_line: 1976
expression: "format!(\"{:#?}\", new_tab_event)" expression: "format!(\"{:#?}\", new_tab_event)"
--- ---
Some( Some(
WriteCharacter( WriteCharacter(
None,
[ [
102, 102,
111, 111,
111, 111,
], ],
false,
1, 1,
), ),
) )

View file

@ -543,8 +543,6 @@ impl WasmBridge {
mut updates: Vec<(Option<PluginId>, Option<ClientId>, Event)>, mut updates: Vec<(Option<PluginId>, Option<ClientId>, Event)>,
shutdown_sender: Sender<()>, shutdown_sender: Sender<()>,
) -> Result<()> { ) -> Result<()> {
let err_context = || "failed to update plugin state".to_string();
let plugins_to_update: Vec<( let plugins_to_update: Vec<(
PluginId, PluginId,
ClientId, ClientId,

View file

@ -1051,7 +1051,7 @@ fn edit_scrollback(env: &ForeignFunctionEnv) {
fn write(env: &ForeignFunctionEnv, bytes: Vec<u8>) { fn write(env: &ForeignFunctionEnv, bytes: Vec<u8>) {
let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name()); let error_msg = || format!("failed to write in plugin {}", env.plugin_env.name());
let action = Action::Write(bytes); let action = Action::Write(None, bytes, false);
apply_action!(action, error_msg, env); apply_action!(action, error_msg, env);
} }

View file

@ -64,12 +64,17 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::ToggleTab(client_id)) .send_to_screen(ScreenInstruction::ToggleTab(client_id))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::Write(val) => { Action::Write(key_with_modifier, raw_bytes, is_kitty_keyboard_protocol) => {
senders senders
.send_to_screen(ScreenInstruction::ClearScroll(client_id)) .send_to_screen(ScreenInstruction::ClearScroll(client_id))
.with_context(err_context)?; .with_context(err_context)?;
senders senders
.send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) .send_to_screen(ScreenInstruction::WriteCharacter(
key_with_modifier,
raw_bytes,
is_kitty_keyboard_protocol,
client_id,
))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::WriteChars(val) => { Action::WriteChars(val) => {
@ -78,7 +83,9 @@ pub(crate) fn route_action(
.with_context(err_context)?; .with_context(err_context)?;
let val = val.into_bytes(); let val = val.into_bytes();
senders senders
.send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) .send_to_screen(ScreenInstruction::WriteCharacter(
None, val, false, client_id,
))
.with_context(err_context)?; .with_context(err_context)?;
}, },
Action::SwitchToMode(mode) => { Action::SwitchToMode(mode) => {

View file

@ -9,7 +9,7 @@ use std::time::Duration;
use log::{debug, warn}; use log::{debug, warn};
use zellij_utils::data::{ use zellij_utils::data::{
Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo, Direction, KeyWithModifier, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo,
}; };
use zellij_utils::errors::prelude::*; use zellij_utils::errors::prelude::*;
use zellij_utils::input::command::RunCommand; use zellij_utils::input::command::RunCommand;
@ -158,7 +158,8 @@ pub enum ScreenInstruction {
ToggleFloatingPanes(ClientId, Option<TerminalAction>), ToggleFloatingPanes(ClientId, Option<TerminalAction>),
HorizontalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId), HorizontalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId),
VerticalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId), VerticalSplit(PaneId, Option<InitialTitle>, HoldForCommand, ClientId),
WriteCharacter(Vec<u8>, ClientId), WriteCharacter(Option<KeyWithModifier>, Vec<u8>, bool, ClientId), // bool ->
// is_kitty_keyboard_protocol
Resize(ClientId, ResizeStrategy), Resize(ClientId, ResizeStrategy),
SwitchFocus(ClientId), SwitchFocus(ClientId),
FocusNextPane(ClientId), FocusNextPane(ClientId),
@ -621,6 +622,7 @@ pub(crate) struct Screen {
arrow_fonts: bool, arrow_fonts: bool,
layout_dir: Option<PathBuf>, layout_dir: Option<PathBuf>,
default_layout_name: Option<String>, default_layout_name: Option<String>,
explicitly_disable_kitty_keyboard_protocol: bool,
} }
impl Screen { impl Screen {
@ -644,6 +646,7 @@ impl Screen {
styled_underlines: bool, styled_underlines: bool,
arrow_fonts: bool, arrow_fonts: bool,
layout_dir: Option<PathBuf>, layout_dir: Option<PathBuf>,
explicitly_disable_kitty_keyboard_protocol: bool,
) -> Self { ) -> Self {
let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_name = mode_info.session_name.clone().unwrap_or_default();
let session_info = SessionInfo::new(session_name.clone()); let session_info = SessionInfo::new(session_name.clone());
@ -684,6 +687,7 @@ impl Screen {
arrow_fonts, arrow_fonts,
resurrectable_sessions, resurrectable_sessions,
layout_dir, layout_dir,
explicitly_disable_kitty_keyboard_protocol,
} }
} }
@ -1232,6 +1236,7 @@ impl Screen {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
self.tabs.insert(tab_index, tab); self.tabs.insert(tab_index, tab);
Ok(()) Ok(())
@ -2317,6 +2322,13 @@ pub(crate) fn screen_thread_main(
config_options.copy_on_select.unwrap_or(true), config_options.copy_on_select.unwrap_or(true),
); );
let styled_underlines = config_options.styled_underlines.unwrap_or(true); let styled_underlines = config_options.styled_underlines.unwrap_or(true);
let explicitly_disable_kitty_keyboard_protocol = config_options
.support_kitty_keyboard_protocol
.map(|e| !e) // this is due to the config options wording, if
// "support_kitty_keyboard_protocol" is true,
// explicitly_disable_kitty_keyboard_protocol is false and vice versa
.unwrap_or(false); // by default, we try to support this if the terminal supports it and
// the program running inside a pane requests it
let thread_senders = bus.senders.clone(); let thread_senders = bus.senders.clone();
let mut screen = Screen::new( let mut screen = Screen::new(
@ -2345,6 +2357,7 @@ pub(crate) fn screen_thread_main(
styled_underlines, styled_underlines,
arrow_fonts, arrow_fonts,
layout_dir, layout_dir,
explicitly_disable_kitty_keyboard_protocol,
); );
let mut pending_tab_ids: HashSet<usize> = HashSet::new(); let mut pending_tab_ids: HashSet<usize> = HashSet::new();
@ -2536,15 +2549,20 @@ pub(crate) fn screen_thread_main(
screen.log_and_report_session_state()?; screen.log_and_report_session_state()?;
screen.render(None)?; screen.render(None)?;
}, },
ScreenInstruction::WriteCharacter(bytes, client_id) => { ScreenInstruction::WriteCharacter(
key_with_modifier,
raw_bytes,
is_kitty_keyboard_protocol,
client_id,
) => {
let mut state_changed = false; let mut state_changed = false;
active_tab_and_connected_client_id!( active_tab_and_connected_client_id!(
screen, screen,
client_id, client_id,
|tab: &mut Tab, client_id: ClientId| { |tab: &mut Tab, client_id: ClientId| {
let write_result = match tab.is_sync_panes_active() { let write_result = match tab.is_sync_panes_active() {
true => tab.write_to_terminals_on_current_tab(bytes, client_id), true => tab.write_to_terminals_on_current_tab(&key_with_modifier, raw_bytes, is_kitty_keyboard_protocol, client_id),
false => tab.write_to_active_terminal(bytes, client_id), false => tab.write_to_active_terminal(&key_with_modifier, raw_bytes, is_kitty_keyboard_protocol, client_id),
}; };
if let Ok(true) = write_result { if let Ok(true) = write_result {
state_changed = true; state_changed = true;

View file

@ -41,6 +41,7 @@ pub struct LayoutApplier<'a> {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
} }
impl<'a> LayoutApplier<'a> { impl<'a> LayoutApplier<'a> {
@ -63,6 +64,7 @@ impl<'a> LayoutApplier<'a> {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
) -> Self { ) -> Self {
let viewport = viewport.clone(); let viewport = viewport.clone();
let senders = senders.clone(); let senders = senders.clone();
@ -94,6 +96,7 @@ impl<'a> LayoutApplier<'a> {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
} }
} }
pub fn apply_layout( pub fn apply_layout(
@ -330,6 +333,7 @@ impl<'a> LayoutApplier<'a> {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
if let Some(pane_initial_contents) = &layout.pane_initial_contents { if let Some(pane_initial_contents) = &layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());
@ -448,6 +452,7 @@ impl<'a> LayoutApplier<'a> {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents {
new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into());

View file

@ -11,7 +11,8 @@ use std::env::temp_dir;
use std::path::PathBuf; use std::path::PathBuf;
use uuid::Uuid; use uuid::Uuid;
use zellij_utils::data::{ use zellij_utils::data::{
Direction, PaneInfo, PermissionStatus, PermissionType, PluginPermission, ResizeStrategy, Direction, KeyWithModifier, PaneInfo, PermissionStatus, PermissionType, PluginPermission,
ResizeStrategy,
}; };
use zellij_utils::errors::prelude::*; use zellij_utils::errors::prelude::*;
use zellij_utils::input::command::RunCommand; use zellij_utils::input::command::RunCommand;
@ -187,6 +188,7 @@ pub(crate) struct Tab {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize)] #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@ -215,7 +217,12 @@ pub trait Pane {
fn handle_pty_bytes(&mut self, _bytes: VteBytes) {} fn handle_pty_bytes(&mut self, _bytes: VteBytes) {}
fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {} fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {}
fn cursor_coordinates(&self) -> Option<(usize, usize)>; fn cursor_coordinates(&self) -> Option<(usize, usize)>;
fn adjust_input_to_terminal(&mut self, _input_bytes: Vec<u8>) -> 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 None
} }
fn position_and_size(&self) -> PaneGeom; fn position_and_size(&self) -> PaneGeom;
@ -487,6 +494,7 @@ pub enum AdjustedInput {
PermissionRequestResult(Vec<PermissionType>, PermissionStatus), PermissionRequestResult(Vec<PermissionType>, PermissionStatus),
CloseThisPane, CloseThisPane,
DropToShellInThisPane { working_dir: Option<PathBuf> }, DropToShellInThisPane { working_dir: Option<PathBuf> },
WriteKeyToPlugin(KeyWithModifier),
} }
pub fn get_next_terminal_position( pub fn get_next_terminal_position(
tiled_panes: &TiledPanes, tiled_panes: &TiledPanes,
@ -537,6 +545,7 @@ impl Tab {
debug: bool, debug: bool,
arrow_fonts: bool, arrow_fonts: bool,
styled_underlines: bool, styled_underlines: bool,
explicitly_disable_kitty_keyboard_protocol: bool,
) -> Self { ) -> Self {
let name = if name.is_empty() { let name = if name.is_empty() {
format!("Tab #{}", index + 1) format!("Tab #{}", index + 1)
@ -627,6 +636,7 @@ impl Tab {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
} }
} }
@ -660,6 +670,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
) )
.apply_layout( .apply_layout(
layout, layout,
@ -723,6 +734,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
) )
.apply_floating_panes_layout_to_existing_panes( .apply_floating_panes_layout_to_existing_panes(
&layout_candidate, &layout_candidate,
@ -779,6 +791,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
) )
.apply_tiled_panes_layout_to_existing_panes( .apply_tiled_panes_layout_to_existing_panes(
&layout_candidate, &layout_candidate,
@ -1084,6 +1097,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
)) as Box<dyn Pane> )) as Box<dyn Pane>
}, },
PaneId::Plugin(plugin_pid) => { PaneId::Plugin(plugin_pid) => {
@ -1146,6 +1160,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the
// constructor so it won't be overrided // constructor so it won't be overrided
@ -1220,6 +1235,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) {
self.floating_panes self.floating_panes
@ -1344,6 +1360,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
self.tiled_panes self.tiled_panes
.split_pane_horizontally(pid, Box::new(new_terminal), client_id); .split_pane_horizontally(pid, Box::new(new_terminal), client_id);
@ -1403,6 +1420,7 @@ impl Tab {
self.debug, self.debug,
self.arrow_fonts, self.arrow_fonts,
self.styled_underlines, self.styled_underlines,
self.explicitly_disable_kitty_keyboard_protocol,
); );
self.tiled_panes self.tiled_panes
.split_pane_vertically(pid, Box::new(new_terminal), client_id); .split_pane_vertically(pid, Box::new(new_terminal), client_id);
@ -1591,7 +1609,7 @@ impl Tab {
let messages_to_pty = terminal_output.drain_messages_to_pty(); let messages_to_pty = terminal_output.drain_messages_to_pty();
let clipboard_update = terminal_output.drain_clipboard_update(); let clipboard_update = terminal_output.drain_clipboard_update();
for message in messages_to_pty { for message in messages_to_pty {
self.write_to_pane_id(message, PaneId::Terminal(pid), None) self.write_to_pane_id_without_preprocessing(message, PaneId::Terminal(pid))
.with_context(err_context)?; .with_context(err_context)?;
} }
if let Some(string) = clipboard_update { if let Some(string) = clipboard_update {
@ -1604,7 +1622,9 @@ impl Tab {
pub fn write_to_terminals_on_current_tab( pub fn write_to_terminals_on_current_tab(
&mut self, &mut self,
input_bytes: Vec<u8>, key_with_modifier: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
raw_input_bytes_are_kitty: bool,
client_id: ClientId, client_id: ClientId,
) -> Result<bool> { ) -> Result<bool> {
// returns true if a UI update should be triggered (eg. when closing a command pane with // returns true if a UI update should be triggered (eg. when closing a command pane with
@ -1613,7 +1633,13 @@ impl Tab {
let pane_ids = self.get_static_and_floating_pane_ids(); let pane_ids = self.get_static_and_floating_pane_ids();
for pane_id in pane_ids { for pane_id in pane_ids {
let ui_change_triggered = self let ui_change_triggered = self
.write_to_pane_id(input_bytes.clone(), pane_id, Some(client_id)) .write_to_pane_id(
key_with_modifier,
raw_input_bytes.clone(),
raw_input_bytes_are_kitty,
pane_id,
Some(client_id),
)
.context("failed to write to terminals on current tab")?; .context("failed to write to terminals on current tab")?;
if ui_change_triggered { if ui_change_triggered {
should_trigger_ui_change = true; should_trigger_ui_change = true;
@ -1624,14 +1650,16 @@ impl Tab {
pub fn write_to_active_terminal( pub fn write_to_active_terminal(
&mut self, &mut self,
input_bytes: Vec<u8>, key_with_modifier: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
raw_input_bytes_are_kitty: bool,
client_id: ClientId, client_id: ClientId,
) -> Result<bool> { ) -> Result<bool> {
// returns true if a UI update should be triggered (eg. if a command pane // returns true if a UI update should be triggered (eg. if a command pane
// was closed with ctrl-c) // was closed with ctrl-c)
let err_context = || { let err_context = || {
format!( format!(
"failed to write to active terminal for client {client_id} - msg: {input_bytes:?}" "failed to write to active terminal for client {client_id} - msg: {raw_input_bytes:?}"
) )
}; };
@ -1651,9 +1679,15 @@ impl Tab {
.get_active_pane_id(client_id) .get_active_pane_id(client_id)
.with_context(err_context)? .with_context(err_context)?
}; };
// Can't use 'err_context' here since it borrows 'input_bytes' // Can't use 'err_context' here since it borrows 'raw_input_bytes'
self.write_to_pane_id(input_bytes, pane_id, Some(client_id)) self.write_to_pane_id(
.with_context(|| format!("failed to write to active terminal for client {client_id}")) key_with_modifier,
raw_input_bytes,
raw_input_bytes_are_kitty,
pane_id,
Some(client_id),
)
.with_context(|| format!("failed to write to active terminal for client {client_id}"))
} }
pub fn write_to_terminal_at( pub fn write_to_terminal_at(
@ -1670,7 +1704,7 @@ impl Tab {
.get_pane_id_at(position, false) .get_pane_id_at(position, false)
.with_context(err_context)?; .with_context(err_context)?;
if let Some(pane_id) = pane_id { if let Some(pane_id) = pane_id {
self.write_to_pane_id(input_bytes, pane_id, Some(client_id)) self.write_to_pane_id(&None, input_bytes, false, pane_id, Some(client_id))
.with_context(err_context)?; .with_context(err_context)?;
return Ok(()); return Ok(());
} }
@ -1680,7 +1714,7 @@ impl Tab {
.get_pane_id_at(position, false) .get_pane_id_at(position, false)
.with_context(err_context)?; .with_context(err_context)?;
if let Some(pane_id) = pane_id { if let Some(pane_id) = pane_id {
self.write_to_pane_id(input_bytes, pane_id, Some(client_id)) self.write_to_pane_id(&None, input_bytes, false, pane_id, Some(client_id))
.with_context(err_context)?; .with_context(err_context)?;
return Ok(()); return Ok(());
} }
@ -1689,7 +1723,9 @@ impl Tab {
pub fn write_to_pane_id( pub fn write_to_pane_id(
&mut self, &mut self,
input_bytes: Vec<u8>, key_with_modifier: &Option<KeyWithModifier>,
raw_input_bytes: Vec<u8>,
raw_input_bytes_are_kitty: bool,
pane_id: PaneId, pane_id: PaneId,
client_id: Option<ClientId>, client_id: Option<ClientId>,
) -> Result<bool> { ) -> Result<bool> {
@ -1720,7 +1756,11 @@ impl Tab {
match pane_id { match pane_id {
PaneId::Terminal(active_terminal_id) => { PaneId::Terminal(active_terminal_id) => {
match active_terminal.adjust_input_to_terminal(input_bytes) { match active_terminal.adjust_input_to_terminal(
key_with_modifier,
raw_input_bytes,
raw_input_bytes_are_kitty,
) {
Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => { Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => {
self.senders self.senders
.send_to_pty_writer(PtyWriteInstruction::Write( .send_to_pty_writer(PtyWriteInstruction::Write(
@ -1758,12 +1798,26 @@ impl Tab {
None => {}, None => {},
} }
}, },
PaneId::Plugin(pid) => match active_terminal.adjust_input_to_terminal(input_bytes) { 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)) => { Some(AdjustedInput::WriteBytesToTerminal(adjusted_input)) => {
let mut plugin_updates = vec![]; let mut plugin_updates = vec![];
for key in parse_keys(&adjusted_input) { for key in parse_keys(&adjusted_input) {
plugin_updates.push((Some(pid), client_id, Event::Key(key))); plugin_updates.push((Some(pid), client_id, Event::Key(key)));
} }
log::info!("plugin_updates: {:?}", plugin_updates);
self.senders self.senders
.send_to_plugin(PluginInstruction::Update(plugin_updates)) .send_to_plugin(PluginInstruction::Update(plugin_updates))
.with_context(err_context)?; .with_context(err_context)?;
@ -1787,6 +1841,32 @@ impl Tab {
} }
Ok(should_update_ui) Ok(should_update_ui)
} }
pub fn write_to_pane_id_without_preprocessing(
&mut self,
raw_input_bytes: Vec<u8>,
pane_id: PaneId,
) -> Result<bool> {
// returns true if we need to update the UI (eg. when a command pane is closed with ctrl-c)
let err_context = || format!("failed to write to pane with id {pane_id:?}");
let mut should_update_ui = false;
match pane_id {
PaneId::Terminal(active_terminal_id) => {
self.senders
.send_to_pty_writer(PtyWriteInstruction::Write(
raw_input_bytes,
active_terminal_id,
))
.with_context(err_context)?;
should_update_ui = true;
},
PaneId::Plugin(_pid) => {
log::error!("Unsupported plugin action");
},
}
Ok(should_update_ui)
}
pub fn get_active_terminal_cursor_position( pub fn get_active_terminal_cursor_position(
&self, &self,
client_id: ClientId, client_id: ClientId,
@ -2889,8 +2969,13 @@ impl Tab {
let relative_position = pane.relative_position(position); let relative_position = pane.relative_position(position);
if let Some(mouse_event) = pane.mouse_left_click(&relative_position, false) { if let Some(mouse_event) = pane.mouse_left_click(&relative_position, false) {
if !pane.position_is_on_frame(position) { if !pane.position_is_on_frame(position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
} }
} else { } else {
pane.start_selection(&relative_position, client_id); pane.start_selection(&relative_position, client_id);
@ -2919,8 +3004,13 @@ impl Tab {
let relative_position = pane.relative_position(position); let relative_position = pane.relative_position(position);
if let Some(mouse_event) = pane.mouse_right_click(&relative_position, false) { if let Some(mouse_event) = pane.mouse_right_click(&relative_position, false) {
if !pane.position_is_on_frame(position) { if !pane.position_is_on_frame(position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
} }
} else { } else {
pane.handle_right_click(&relative_position, client_id); pane.handle_right_click(&relative_position, client_id);
@ -2946,8 +3036,13 @@ impl Tab {
let relative_position = pane.relative_position(position); let relative_position = pane.relative_position(position);
if let Some(mouse_event) = pane.mouse_middle_click(&relative_position, false) { if let Some(mouse_event) = pane.mouse_middle_click(&relative_position, false) {
if !pane.position_is_on_frame(position) { if !pane.position_is_on_frame(position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
} }
} }
}; };
@ -3006,7 +3101,7 @@ impl Tab {
); );
if let Some(mouse_event) = active_pane.mouse_right_click_release(&relative_position) { if let Some(mouse_event) = active_pane.mouse_right_click_release(&relative_position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(&None, mouse_event.into_bytes(), false, client_id)
.with_context(err_context)?; .with_context(err_context)?;
} }
} }
@ -3039,7 +3134,7 @@ impl Tab {
); );
if let Some(mouse_event) = active_pane.mouse_middle_click_release(&relative_position) { if let Some(mouse_event) = active_pane.mouse_middle_click_release(&relative_position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(&None, mouse_event.into_bytes(), false, client_id)
.with_context(err_context)?; .with_context(err_context)?;
} }
} }
@ -3084,7 +3179,7 @@ impl Tab {
); );
if let Some(mouse_event) = active_pane.mouse_left_click_release(&relative_position) { if let Some(mouse_event) = active_pane.mouse_left_click_release(&relative_position) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(&None, mouse_event.into_bytes(), false, client_id)
.with_context(err_context)?; .with_context(err_context)?;
} else { } else {
let relative_position = active_pane.relative_position(position); let relative_position = active_pane.relative_position(position);
@ -3163,8 +3258,13 @@ impl Tab {
.min(active_pane.get_content_rows() as isize), .min(active_pane.get_content_rows() as isize),
); );
if let Some(mouse_event) = active_pane.mouse_left_click(&relative_position, true) { if let Some(mouse_event) = active_pane.mouse_left_click(&relative_position, true) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
return Ok(true); // we need to re-render in this case so the selection disappears return Ok(true); // we need to re-render in this case so the selection disappears
} }
} else if selecting { } else if selecting {
@ -3210,8 +3310,13 @@ impl Tab {
.min(active_pane.get_content_rows() as isize), .min(active_pane.get_content_rows() as isize),
); );
if let Some(mouse_event) = active_pane.mouse_right_click(&relative_position, true) { if let Some(mouse_event) = active_pane.mouse_right_click(&relative_position, true) {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
return Ok(true); // we need to re-render in this case so the selection disappears return Ok(true); // we need to re-render in this case so the selection disappears
} }
} }
@ -3254,8 +3359,13 @@ impl Tab {
); );
if let Some(mouse_event) = active_pane.mouse_middle_click(&relative_position, true) if let Some(mouse_event) = active_pane.mouse_middle_click(&relative_position, true)
{ {
self.write_to_active_terminal(mouse_event.into_bytes(), client_id) self.write_to_active_terminal(
.with_context(err_context)?; &None,
mouse_event.into_bytes(),
false,
client_id,
)
.with_context(err_context)?;
return Ok(true); // we need to re-render in this case so the selection disappears return Ok(true); // we need to re-render in this case so the selection disappears
} }
} }

View file

@ -226,6 +226,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -251,6 +252,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -303,6 +305,7 @@ fn create_new_tab_with_swap_layouts(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -328,6 +331,7 @@ fn create_new_tab_with_swap_layouts(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let ( let (
base_layout, base_layout,
@ -382,6 +386,7 @@ fn create_new_tab_with_os_api(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -407,6 +412,7 @@ fn create_new_tab_with_os_api(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -447,6 +453,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -472,6 +479,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str)
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let pane_ids = tab_layout let pane_ids = tab_layout
.extract_run_instructions() .extract_run_instructions()
@ -526,6 +534,7 @@ fn create_new_tab_with_mock_pty_writer(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -551,6 +560,7 @@ fn create_new_tab_with_mock_pty_writer(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -596,6 +606,7 @@ fn create_new_tab_with_sixel_support(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -621,6 +632,7 @@ fn create_new_tab_with_sixel_support(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -659,6 +671,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -671,6 +684,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette:
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {
@ -694,6 +708,7 @@ fn take_snapshot_with_sixel(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -706,6 +721,7 @@ fn take_snapshot_with_sixel(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {
@ -726,6 +742,7 @@ fn take_snapshot_and_cursor_position(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut grid = Grid::new( let mut grid = Grid::new(
rows, rows,
columns, columns,
@ -738,6 +755,7 @@ fn take_snapshot_and_cursor_position(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let mut vte_parser = vte::Parser::new(); let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() { for &byte in ansi_instructions.as_bytes() {
@ -2882,11 +2900,11 @@ fn pane_bracketed_paste_ignored_when_not_in_bracketed_paste_mode() {
let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~ let bracketed_paste_start = vec![27, 91, 50, 48, 48, 126]; // \u{1b}[200~
let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201 let bracketed_paste_end = vec![27, 91, 50, 48, 49, 126]; // \u{1b}[201
tab.write_to_active_terminal(bracketed_paste_start, client_id) tab.write_to_active_terminal(&None, bracketed_paste_start, false, client_id)
.unwrap(); .unwrap();
tab.write_to_active_terminal("test".as_bytes().to_vec(), client_id) tab.write_to_active_terminal(&None, "test".as_bytes().to_vec(), false, client_id)
.unwrap(); .unwrap();
tab.write_to_active_terminal(bracketed_paste_end, client_id) tab.write_to_active_terminal(&None, bracketed_paste_end, false, client_id)
.unwrap(); .unwrap();
pty_instruction_bus.exit(); pty_instruction_bus.exit();

View file

@ -167,6 +167,7 @@ fn create_new_tab(size: Size) -> Tab {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -192,6 +193,7 @@ fn create_new_tab(size: Size) -> Tab {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -229,6 +231,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -254,6 +257,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab {
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let mut new_terminal_ids = vec![]; let mut new_terminal_ids = vec![];
for i in 0..layout.extract_run_instructions().len() { for i in 0..layout.extract_run_instructions().len() {
@ -297,6 +301,7 @@ fn create_new_tab_with_cell_size(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut tab = Tab::new( let mut tab = Tab::new(
index, index,
position, position,
@ -322,6 +327,7 @@ fn create_new_tab_with_cell_size(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
tab.apply_layout( tab.apply_layout(
TiledPaneLayout::default(), TiledPaneLayout::default(),
@ -352,8 +358,14 @@ fn write_to_suppressed_pane() {
// Make sure it's suppressed now // Make sure it's suppressed now
tab.suppressed_panes.get(&PaneId::Terminal(2)).unwrap(); tab.suppressed_panes.get(&PaneId::Terminal(2)).unwrap();
// Write content to it // Write content to it
tab.write_to_pane_id(vec![34, 127, 31, 82, 17, 182], PaneId::Terminal(2), None) tab.write_to_pane_id(
.unwrap(); &None,
vec![34, 127, 31, 82, 17, 182],
false,
PaneId::Terminal(2),
None,
)
.unwrap();
} }
#[test] #[test]

View file

@ -70,6 +70,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
let debug = false; let debug = false;
let arrow_fonts = true; let arrow_fonts = true;
let styled_underlines = true; let styled_underlines = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let mut grid = Grid::new( let mut grid = Grid::new(
screen_size.rows, screen_size.rows,
screen_size.cols, screen_size.cols,
@ -82,6 +83,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
debug, debug,
arrow_fonts, arrow_fonts,
styled_underlines, styled_underlines,
explicitly_disable_kitty_keyboard_protocol,
); );
let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events
.filter_map(|server_instruction| { .filter_map(|server_instruction| {
@ -252,6 +254,7 @@ fn create_new_screen(size: Size) -> Screen {
let debug = false; let debug = false;
let styled_underlines = true; let styled_underlines = true;
let arrow_fonts = true; let arrow_fonts = true;
let explicitly_disable_kitty_keyboard_protocol = false;
let screen = Screen::new( let screen = Screen::new(
bus, bus,
&client_attributes, &client_attributes,
@ -271,6 +274,7 @@ fn create_new_screen(size: Size) -> Screen {
styled_underlines, styled_underlines,
arrow_fonts, arrow_fonts,
layout_dir, layout_dir,
explicitly_disable_kitty_keyboard_protocol,
); );
screen screen
} }

View file

@ -43,6 +43,7 @@ thiserror = "1.0.30"
unicode-width = "0.1.8" unicode-width = "0.1.8"
url = { version = "2.2.2", features = ["serde"] } url = { version = "2.2.2", features = ["serde"] }
uuid = { version = "1.4.1", features = ["serde", "v4"] } uuid = { version = "1.4.1", features = ["serde", "v4"] }
bitflags = "2.5.0"
vte = { version = "0.11.0", default-features = false } vte = { version = "0.11.0", default-features = false }
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]

View file

@ -363,3 +363,8 @@ plugins {
// Default: false // Default: false
// //
// disable_session_metadata true // disable_session_metadata true
// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it)
// Default: true (if the host terminal supports it)
//
// support_kitty_keyboard_protocol false

View file

@ -3,6 +3,8 @@
pub struct Key { pub struct Key {
#[prost(enumeration = "key::KeyModifier", optional, tag = "1")] #[prost(enumeration = "key::KeyModifier", optional, tag = "1")]
pub modifier: ::core::option::Option<i32>, pub modifier: ::core::option::Option<i32>,
#[prost(enumeration = "key::KeyModifier", repeated, tag = "4")]
pub additional_modifiers: ::prost::alloc::vec::Vec<i32>,
#[prost(oneof = "key::MainKey", tags = "2, 3")] #[prost(oneof = "key::MainKey", tags = "2, 3")]
pub main_key: ::core::option::Option<key::MainKey>, pub main_key: ::core::option::Option<key::MainKey>,
} }
@ -23,6 +25,8 @@ pub mod key {
pub enum KeyModifier { pub enum KeyModifier {
Ctrl = 0, Ctrl = 0,
Alt = 1, Alt = 1,
Shift = 2,
Super = 3,
} }
impl KeyModifier { impl KeyModifier {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -33,6 +37,8 @@ pub mod key {
match self { match self {
KeyModifier::Ctrl => "CTRL", KeyModifier::Ctrl => "CTRL",
KeyModifier::Alt => "ALT", KeyModifier::Alt => "ALT",
KeyModifier::Shift => "SHIFT",
KeyModifier::Super => "SUPER",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -40,6 +46,8 @@ pub mod key {
match value { match value {
"CTRL" => Some(Self::Ctrl), "CTRL" => Some(Self::Ctrl),
"ALT" => Some(Self::Alt), "ALT" => Some(Self::Alt),
"SHIFT" => Some(Self::Shift),
"SUPER" => Some(Self::Super),
_ => None, _ => None,
} }
} }
@ -82,6 +90,13 @@ pub mod key {
F12 = 22, F12 = 22,
Tab = 23, Tab = 23,
Esc = 24, Esc = 24,
CapsLock = 25,
ScrollLock = 26,
NumLock = 27,
PrintScreen = 28,
Pause = 29,
Menu = 30,
Enter = 31,
} }
impl NamedKey { impl NamedKey {
/// String value of the enum field names used in the ProtoBuf definition. /// String value of the enum field names used in the ProtoBuf definition.
@ -115,6 +130,13 @@ pub mod key {
NamedKey::F12 => "F12", NamedKey::F12 => "F12",
NamedKey::Tab => "Tab", NamedKey::Tab => "Tab",
NamedKey::Esc => "Esc", NamedKey::Esc => "Esc",
NamedKey::CapsLock => "CapsLock",
NamedKey::ScrollLock => "ScrollLock",
NamedKey::NumLock => "NumLock",
NamedKey::PrintScreen => "PrintScreen",
NamedKey::Pause => "Pause",
NamedKey::Menu => "Menu",
NamedKey::Enter => "Enter",
} }
} }
/// Creates an enum from field names used in the ProtoBuf definition. /// Creates an enum from field names used in the ProtoBuf definition.
@ -145,6 +167,13 @@ pub mod key {
"F12" => Some(Self::F12), "F12" => Some(Self::F12),
"Tab" => Some(Self::Tab), "Tab" => Some(Self::Tab),
"Esc" => Some(Self::Esc), "Esc" => Some(Self::Esc),
"CapsLock" => Some(Self::CapsLock),
"ScrollLock" => Some(Self::ScrollLock),
"NumLock" => Some(Self::NumLock),
"PrintScreen" => Some(Self::PrintScreen),
"Pause" => Some(Self::Pause),
"Menu" => Some(Self::Menu),
"Enter" => Some(Self::Enter),
_ => None, _ => None,
} }
} }

View file

@ -3,14 +3,20 @@ use crate::input::config::ConversionError;
use crate::input::layout::SplitSize; use crate::input::layout::SplitSize;
use clap::ArgEnum; use clap::ArgEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt; use std::fmt;
use std::fs::Metadata; use std::fs::Metadata;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::{self, FromStr};
use std::time::Duration; use std::time::Duration;
use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString}; use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString};
#[cfg(not(target_family = "wasm"))]
use termwiz::{
escape::csi::KittyKeyboardFlags,
input::{KeyCode, KeyCodeEncodeModes, KeyboardEncoding, Modifiers},
};
pub type ClientId = u16; // TODO: merge with crate type? pub type ClientId = u16; // TODO: merge with crate type?
pub fn client_id_to_colors( pub fn client_id_to_colors(
@ -37,13 +43,107 @@ pub fn single_client_color(colors: Palette) -> (PaletteColor, PaletteColor) {
(colors.green, colors.black) (colors.green, colors.black)
} }
// TODO: Add a shortened string representation (beyond `Display::fmt` below) that can be used when impl FromStr for KeyWithModifier {
// screen space is scarce. Useful for e.g. "ENTER", "SPACE", "TAB" to display as Unicode type Err = Box<dyn std::error::Error>;
// representations instead. fn from_str(key_str: &str) -> Result<Self, Self::Err> {
// NOTE: Do not reorder the key variants since that influences what the `status_bar` plugin let mut key_string_parts: Vec<&str> = key_str.split_ascii_whitespace().collect();
// displays! let bare_key: BareKey = BareKey::from_str(key_string_parts.pop().ok_or("empty key")?)?;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)] let mut key_modifiers: BTreeSet<KeyModifier> = BTreeSet::new();
pub enum Key { for stringified_modifier in key_string_parts {
key_modifiers.insert(KeyModifier::from_str(stringified_modifier)?);
}
Ok(KeyWithModifier {
bare_key,
key_modifiers,
})
}
}
#[derive(Debug, Clone, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub struct KeyWithModifier {
pub bare_key: BareKey,
pub key_modifiers: BTreeSet<KeyModifier>,
}
impl PartialEq for KeyWithModifier {
fn eq(&self, other: &Self) -> bool {
match (self.bare_key, other.bare_key) {
(BareKey::Char(self_char), BareKey::Char(other_char))
if self_char.to_ascii_lowercase() == other_char.to_ascii_lowercase() =>
{
let mut self_cloned = self.clone();
let mut other_cloned = other.clone();
if self_char.is_ascii_uppercase() {
self_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
self_cloned.key_modifiers.insert(KeyModifier::Shift);
}
if other_char.is_ascii_uppercase() {
other_cloned.bare_key = BareKey::Char(self_char.to_ascii_lowercase());
other_cloned.key_modifiers.insert(KeyModifier::Shift);
}
self_cloned.bare_key == other_cloned.bare_key
&& self_cloned.key_modifiers == other_cloned.key_modifiers
},
_ => self.bare_key == other.bare_key && self.key_modifiers == other.key_modifiers,
}
}
}
impl Hash for KeyWithModifier {
fn hash<H: Hasher>(&self, state: &mut H) {
match self.bare_key {
BareKey::Char(character) if character.is_ascii_uppercase() => {
let mut to_hash = self.clone();
to_hash.bare_key = BareKey::Char(character.to_ascii_lowercase());
to_hash.key_modifiers.insert(KeyModifier::Shift);
to_hash.bare_key.hash(state);
to_hash.key_modifiers.hash(state);
},
_ => {
self.bare_key.hash(state);
self.key_modifiers.hash(state);
},
}
}
}
impl fmt::Display for KeyWithModifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.key_modifiers.is_empty() {
write!(f, "{}", self.bare_key)
} else {
write!(
f,
"{} {}",
self.key_modifiers
.iter()
.map(|m| m.to_string())
.collect::<Vec<_>>()
.join("-"),
self.bare_key
)
}
}
}
#[cfg(not(target_family = "wasm"))]
impl Into<Modifiers> for &KeyModifier {
fn into(self) -> Modifiers {
match self {
KeyModifier::Shift => Modifiers::SHIFT,
KeyModifier::Alt => Modifiers::ALT,
KeyModifier::Ctrl => Modifiers::CTRL,
KeyModifier::Super => Modifiers::SUPER,
KeyModifier::Hyper => Modifiers::NONE,
KeyModifier::Meta => Modifiers::NONE,
KeyModifier::CapsLock => Modifiers::NONE,
KeyModifier::NumLock => Modifiers::NONE,
}
}
}
#[derive(Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
pub enum BareKey {
PageDown, PageDown,
PageUp, PageUp,
Left, Left,
@ -57,149 +157,398 @@ pub enum Key {
Insert, Insert,
F(u8), F(u8),
Char(char), Char(char),
Alt(CharOrArrow), Tab,
Ctrl(char),
BackTab,
Null,
Esc, Esc,
AltF(u8), Enter,
CtrlF(u8), CapsLock,
ScrollLock,
NumLock,
PrintScreen,
Pause,
Menu,
} }
impl FromStr for Key { impl fmt::Display for BareKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BareKey::PageDown => write!(f, "PgDn"),
BareKey::PageUp => write!(f, "PgUp"),
BareKey::Left => write!(f, ""),
BareKey::Down => write!(f, ""),
BareKey::Up => write!(f, ""),
BareKey::Right => write!(f, ""),
BareKey::Home => write!(f, "HOME"),
BareKey::End => write!(f, "END"),
BareKey::Backspace => write!(f, "BACKSPACE"),
BareKey::Delete => write!(f, "DEL"),
BareKey::Insert => write!(f, "INS"),
BareKey::F(index) => write!(f, "F{}", index),
BareKey::Char(' ') => write!(f, "SPACE"),
BareKey::Char(character) => write!(f, "{}", character),
BareKey::Tab => write!(f, "TAB"),
BareKey::Esc => write!(f, "ESC"),
BareKey::Enter => write!(f, "ENTER"),
BareKey::CapsLock => write!(f, "CAPSlOCK"),
BareKey::ScrollLock => write!(f, "SCROLLlOCK"),
BareKey::NumLock => write!(f, "NUMLOCK"),
BareKey::PrintScreen => write!(f, "PRINTSCREEN"),
BareKey::Pause => write!(f, "PAUSE"),
BareKey::Menu => write!(f, "MENU"),
}
}
}
impl FromStr for BareKey {
type Err = Box<dyn std::error::Error>; type Err = Box<dyn std::error::Error>;
fn from_str(key_str: &str) -> Result<Self, Self::Err> { fn from_str(key_str: &str) -> Result<Self, Self::Err> {
let mut modifier: Option<&str> = None; match key_str.to_ascii_lowercase().as_str() {
let mut main_key: Option<&str> = None; "pagedown" => Ok(BareKey::PageDown),
for (index, part) in key_str.split_ascii_whitespace().enumerate() { "pageup" => Ok(BareKey::PageUp),
if index == 0 && (part == "Ctrl" || part == "Alt") { "left" => Ok(BareKey::Left),
modifier = Some(part); "down" => Ok(BareKey::Down),
} else if main_key.is_none() { "up" => Ok(BareKey::Up),
main_key = Some(part) "right" => Ok(BareKey::Right),
"home" => Ok(BareKey::Home),
"end" => Ok(BareKey::End),
"backspace" => Ok(BareKey::Backspace),
"delete" => Ok(BareKey::Delete),
"insert" => Ok(BareKey::Insert),
"f1" => Ok(BareKey::F(1)),
"f2" => Ok(BareKey::F(2)),
"f3" => Ok(BareKey::F(3)),
"f4" => Ok(BareKey::F(4)),
"f5" => Ok(BareKey::F(5)),
"f6" => Ok(BareKey::F(6)),
"f7" => Ok(BareKey::F(7)),
"f8" => Ok(BareKey::F(8)),
"f9" => Ok(BareKey::F(9)),
"f10" => Ok(BareKey::F(10)),
"f11" => Ok(BareKey::F(11)),
"f12" => Ok(BareKey::F(12)),
"tab" => Ok(BareKey::Tab),
"esc" => Ok(BareKey::Esc),
"enter" => Ok(BareKey::Enter),
"capsLock" => Ok(BareKey::CapsLock),
"scrollLock" => Ok(BareKey::ScrollLock),
"numlock" => Ok(BareKey::NumLock),
"printscreen" => Ok(BareKey::PrintScreen),
"pause" => Ok(BareKey::Pause),
"menu" => Ok(BareKey::Menu),
"space" => Ok(BareKey::Char(' ')),
_ => {
if key_str.chars().count() == 1 {
if let Some(character) = key_str.chars().next() {
return Ok(BareKey::Char(character));
}
}
Err("unsupported key".into())
},
}
}
}
#[derive(
Eq, Clone, Copy, Debug, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, ToString,
)]
pub enum KeyModifier {
Ctrl,
Alt,
Shift,
Super,
Hyper,
Meta,
CapsLock,
NumLock,
}
impl FromStr for KeyModifier {
type Err = Box<dyn std::error::Error>;
fn from_str(key_str: &str) -> Result<Self, Self::Err> {
match key_str.to_ascii_lowercase().as_str() {
"shift" => Ok(KeyModifier::Shift),
"alt" => Ok(KeyModifier::Alt),
"ctrl" => Ok(KeyModifier::Ctrl),
"super" => Ok(KeyModifier::Super),
_ => Err("unsupported modifier".into()),
}
}
}
impl BareKey {
pub fn from_bytes_with_u(bytes: &[u8]) -> Option<Self> {
match str::from_utf8(bytes) {
Ok("27") => Some(BareKey::Esc),
Ok("13") => Some(BareKey::Enter),
Ok("9") => Some(BareKey::Tab),
Ok("127") => Some(BareKey::Backspace),
Ok("57358") => Some(BareKey::CapsLock),
Ok("57359") => Some(BareKey::ScrollLock),
Ok("57360") => Some(BareKey::NumLock),
Ok("57361") => Some(BareKey::PrintScreen),
Ok("57362") => Some(BareKey::Pause),
Ok("57363") => Some(BareKey::Menu),
Ok(num) => u8::from_str_radix(num, 10)
.ok()
.map(|n| BareKey::Char((n as char).to_ascii_lowercase())),
_ => None,
}
}
pub fn from_bytes_with_tilde(bytes: &[u8]) -> Option<Self> {
match str::from_utf8(bytes) {
Ok("2") => Some(BareKey::Insert),
Ok("3") => Some(BareKey::Delete),
Ok("5") => Some(BareKey::PageUp),
Ok("6") => Some(BareKey::PageDown),
Ok("7") => Some(BareKey::Home),
Ok("8") => Some(BareKey::End),
Ok("11") => Some(BareKey::F(1)),
Ok("12") => Some(BareKey::F(2)),
Ok("13") => Some(BareKey::F(3)),
Ok("14") => Some(BareKey::F(4)),
Ok("15") => Some(BareKey::F(5)),
Ok("17") => Some(BareKey::F(6)),
Ok("18") => Some(BareKey::F(7)),
Ok("19") => Some(BareKey::F(8)),
Ok("20") => Some(BareKey::F(9)),
Ok("21") => Some(BareKey::F(10)),
Ok("23") => Some(BareKey::F(11)),
Ok("24") => Some(BareKey::F(12)),
_ => None,
}
}
pub fn from_bytes_with_no_ending_byte(bytes: &[u8]) -> Option<Self> {
match str::from_utf8(bytes) {
Ok("1D") | Ok("D") => Some(BareKey::Left),
Ok("1C") | Ok("C") => Some(BareKey::Right),
Ok("1A") | Ok("A") => Some(BareKey::Up),
Ok("1B") | Ok("B") => Some(BareKey::Down),
Ok("1H") | Ok("H") => Some(BareKey::Home),
Ok("1F") | Ok("F") => Some(BareKey::End),
Ok("1P") | Ok("P") => Some(BareKey::F(1)),
Ok("1Q") | Ok("Q") => Some(BareKey::F(2)),
Ok("1S") | Ok("S") => Some(BareKey::F(4)),
_ => None,
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ModifierFlags: u8 {
const SHIFT = 0b0000_0001;
const ALT = 0b0000_0010;
const CONTROL = 0b0000_0100;
const SUPER = 0b0000_1000;
const HYPER = 0b0001_0000;
const META = 0b0010_0000;
const CAPS_LOCK = 0b0100_0000;
const NUM_LOCK = 0b1000_0000;
}
}
impl KeyModifier {
pub fn from_bytes(bytes: &[u8]) -> BTreeSet<KeyModifier> {
let modifier_flags = str::from_utf8(bytes)
.ok() // convert to string: (eg. "16")
.and_then(|s| u8::from_str_radix(&s, 10).ok()) // convert to u8: (eg. 16)
.map(|s| s.saturating_sub(1)) // subtract 1: (eg. 15)
.and_then(|b| ModifierFlags::from_bits(b)); // bitflags: (0b0000_1111: Shift, Alt, Control, Super)
let mut key_modifiers = BTreeSet::new();
if let Some(modifier_flags) = modifier_flags {
for name in modifier_flags.iter() {
match name {
ModifierFlags::SHIFT => key_modifiers.insert(KeyModifier::Shift),
ModifierFlags::ALT => key_modifiers.insert(KeyModifier::Alt),
ModifierFlags::CONTROL => key_modifiers.insert(KeyModifier::Ctrl),
ModifierFlags::SUPER => key_modifiers.insert(KeyModifier::Super),
ModifierFlags::HYPER => key_modifiers.insert(KeyModifier::Hyper),
ModifierFlags::META => key_modifiers.insert(KeyModifier::Meta),
ModifierFlags::CAPS_LOCK => key_modifiers.insert(KeyModifier::CapsLock),
ModifierFlags::NUM_LOCK => key_modifiers.insert(KeyModifier::NumLock),
_ => false,
};
} }
} }
match (modifier, main_key) { key_modifiers
(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()),
}
} }
} }
fn parse_main_key( impl KeyWithModifier {
main_key: &str, pub fn new(bare_key: BareKey) -> Self {
key_str: &str, KeyWithModifier {
to_char_key: impl FnOnce(char) -> Key, bare_key,
to_fn_key: impl FnOnce(u8) -> Key, key_modifiers: BTreeSet::new(),
) -> Result<Key, Box<dyn std::error::Error>> { }
let mut key_chars = main_key.chars(); }
let key_count = main_key.chars().count(); pub fn new_with_modifiers(bare_key: BareKey, key_modifiers: BTreeSet<KeyModifier>) -> Self {
if key_count == 1 { KeyWithModifier {
let key_char = key_chars.next().unwrap(); bare_key,
Ok(to_char_key(key_char)) key_modifiers,
} else if key_count > 1 { }
if let Some(first_char) = key_chars.next() { }
if first_char == 'F' { pub fn with_shift_modifier(mut self) -> Self {
let f_index: String = key_chars.collect(); self.key_modifiers.insert(KeyModifier::Shift);
let f_index: u8 = f_index self
.parse() }
.map_err(|e| format!("Failed to parse F index: {}", e))?; pub fn with_alt_modifier(mut self) -> Self {
if f_index >= 1 && f_index <= 12 { self.key_modifiers.insert(KeyModifier::Alt);
return Ok(to_fn_key(f_index)); self
} }
pub fn with_ctrl_modifier(mut self) -> Self {
self.key_modifiers.insert(KeyModifier::Ctrl);
self
}
pub fn with_super_modifier(mut self) -> Self {
self.key_modifiers.insert(KeyModifier::Super);
self
}
pub fn from_bytes_with_u(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
// CSI number ; modifiers u
let bare_key = BareKey::from_bytes_with_u(number_bytes);
match bare_key {
Some(bare_key) => {
let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
Some(KeyWithModifier {
bare_key,
key_modifiers,
})
},
_ => None,
}
}
pub fn from_bytes_with_tilde(number_bytes: &[u8], modifier_bytes: &[u8]) -> Option<Self> {
// CSI number ; modifiers ~
let bare_key = BareKey::from_bytes_with_tilde(number_bytes);
match bare_key {
Some(bare_key) => {
let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
Some(KeyWithModifier {
bare_key,
key_modifiers,
})
},
_ => None,
}
}
pub fn from_bytes_with_no_ending_byte(
number_bytes: &[u8],
modifier_bytes: &[u8],
) -> Option<Self> {
// CSI 1; modifiers [ABCDEFHPQS]
let bare_key = BareKey::from_bytes_with_no_ending_byte(number_bytes);
match bare_key {
Some(bare_key) => {
let key_modifiers = KeyModifier::from_bytes(modifier_bytes);
Some(KeyWithModifier {
bare_key,
key_modifiers,
})
},
_ => None,
}
}
pub fn strip_common_modifiers(&self, common_modifiers: &Vec<KeyModifier>) -> Self {
let common_modifiers: BTreeSet<&KeyModifier> = common_modifiers.into_iter().collect();
KeyWithModifier {
bare_key: self.bare_key.clone(),
key_modifiers: self
.key_modifiers
.iter()
.filter(|m| !common_modifiers.contains(m))
.cloned()
.collect(),
}
}
pub fn is_key_without_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.is_empty()
}
pub fn is_key_with_ctrl_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Ctrl)
}
pub fn is_key_with_alt_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Alt)
}
pub fn is_key_with_shift_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Shift)
}
pub fn is_key_with_super_modifier(&self, key: BareKey) -> bool {
self.bare_key == key && self.key_modifiers.contains(&KeyModifier::Super)
}
#[cfg(not(target_family = "wasm"))]
pub fn to_termwiz_modifiers(&self) -> Modifiers {
let mut modifiers = Modifiers::empty();
for modifier in &self.key_modifiers {
modifiers.set(modifier.into(), true);
}
modifiers
}
#[cfg(not(target_family = "wasm"))]
pub fn to_termwiz_keycode(&self) -> KeyCode {
match self.bare_key {
BareKey::PageDown => KeyCode::PageDown,
BareKey::PageUp => KeyCode::PageUp,
BareKey::Left => KeyCode::LeftArrow,
BareKey::Down => KeyCode::DownArrow,
BareKey::Up => KeyCode::UpArrow,
BareKey::Right => KeyCode::RightArrow,
BareKey::Home => KeyCode::Home,
BareKey::End => KeyCode::End,
BareKey::Backspace => KeyCode::Backspace,
BareKey::Delete => KeyCode::Delete,
BareKey::Insert => KeyCode::Insert,
BareKey::F(index) => KeyCode::Function(index),
BareKey::Char(character) => KeyCode::Char(character),
BareKey::Tab => KeyCode::Tab,
BareKey::Esc => KeyCode::Escape,
BareKey::Enter => KeyCode::Enter,
BareKey::CapsLock => KeyCode::CapsLock,
BareKey::ScrollLock => KeyCode::ScrollLock,
BareKey::NumLock => KeyCode::NumLock,
BareKey::PrintScreen => KeyCode::PrintScreen,
BareKey::Pause => KeyCode::Pause,
BareKey::Menu => KeyCode::Menu,
}
}
#[cfg(not(target_family = "wasm"))]
pub fn serialize_non_kitty(&self) -> Option<String> {
let modifiers = self.to_termwiz_modifiers();
let key_code_encode_modes = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Xterm,
// all these flags are false because they have been dealt with before this
// serialization
application_cursor_keys: false,
newline_mode: false,
modify_other_keys: None,
};
self.to_termwiz_keycode()
.encode(modifiers, key_code_encode_modes, true)
.ok()
}
#[cfg(not(target_family = "wasm"))]
pub fn serialize_kitty(&self) -> Option<String> {
let modifiers = self.to_termwiz_modifiers();
let key_code_encode_modes = KeyCodeEncodeModes {
encoding: KeyboardEncoding::Kitty(KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES),
// all these flags are false because they have been dealt with before this
// serialization
application_cursor_keys: false,
newline_mode: false,
modify_other_keys: None,
};
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;
} }
} }
Err(format!("Failed to parse key: {}", key_str).into()) true
} 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),
}
} }
} }
@ -493,7 +842,7 @@ pub enum Event {
TabUpdate(Vec<TabInfo>), TabUpdate(Vec<TabInfo>),
PaneUpdate(PaneManifest), PaneUpdate(PaneManifest),
/// A key was pressed while the user is focused on this plugin's pane /// A key was pressed while the user is focused on this plugin's pane
Key(Key), Key(KeyWithModifier),
/// A mouse event happened while the user is focused on this plugin's pane /// A mouse event happened while the user is focused on this plugin's pane
Mouse(Mouse), Mouse(Mouse),
/// A timer expired set by the `set_timeout` method exported by `zellij-tile`. /// A timer expired set by the `set_timeout` method exported by `zellij-tile`.
@ -756,7 +1105,7 @@ pub struct Style {
} }
// FIXME: Poor devs hashtable since HashTable can't derive `Default`... // FIXME: Poor devs hashtable since HashTable can't derive `Default`...
pub type KeybindsVec = Vec<(InputMode, Vec<(Key, Vec<Action>)>)>; pub type KeybindsVec = Vec<(InputMode, Vec<(KeyWithModifier, Vec<Action>)>)>;
/// Provides information helpful in rendering the Zellij controls for UI bars /// Provides information helpful in rendering the Zellij controls for UI bars
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -769,11 +1118,11 @@ pub struct ModeInfo {
} }
impl ModeInfo { impl ModeInfo {
pub fn get_mode_keybinds(&self) -> Vec<(Key, Vec<Action>)> { pub fn get_mode_keybinds(&self) -> Vec<(KeyWithModifier, Vec<Action>)> {
self.get_keybinds_for_mode(self.mode) self.get_keybinds_for_mode(self.mode)
} }
pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(Key, Vec<Action>)> { pub fn get_keybinds_for_mode(&self, mode: InputMode) -> Vec<(KeyWithModifier, Vec<Action>)> {
for (vec_mode, map) in &self.keybinds { for (vec_mode, map) in &self.keybinds {
if mode == *vec_mode { if mode == *vec_mode {
return map.to_vec(); return map.to_vec();

View file

@ -6,7 +6,7 @@ use super::layout::{
SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout, SwapFloatingLayout, SwapTiledLayout, TiledPaneLayout,
}; };
use crate::cli::CliAction; use crate::cli::CliAction;
use crate::data::{Direction, Resize}; use crate::data::{Direction, KeyWithModifier, Resize};
use crate::data::{FloatingPaneCoordinates, InputMode}; use crate::data::{FloatingPaneCoordinates, InputMode};
use crate::home::{find_default_config_dir, get_layout_dir}; use crate::home::{find_default_config_dir, get_layout_dir};
use crate::input::config::{Config, ConfigError, KdlError}; use crate::input::config::{Config, ConfigError, KdlError};
@ -102,7 +102,7 @@ pub enum Action {
/// Quit Zellij. /// Quit Zellij.
Quit, Quit,
/// Write to the terminal. /// Write to the terminal.
Write(Vec<u8>), Write(Option<KeyWithModifier>, Vec<u8>, bool), // bool -> is_kitty_keyboard_protocol
/// Write Characters to the terminal. /// Write Characters to the terminal.
WriteChars(String), WriteChars(String),
/// Switch to the specified input mode. /// Switch to the specified input mode.
@ -317,7 +317,7 @@ impl Action {
config: Option<Config>, config: Option<Config>,
) -> Result<Vec<Action>, String> { ) -> Result<Vec<Action>, String> {
match cli_action { match cli_action {
CliAction::Write { bytes } => Ok(vec![Action::Write(bytes)]), CliAction::Write { bytes } => Ok(vec![Action::Write(None, bytes, false)]),
CliAction::WriteChars { chars } => Ok(vec![Action::WriteChars(chars)]), CliAction::WriteChars { chars } => Ok(vec![Action::WriteChars(chars)]),
CliAction::Resize { resize, direction } => Ok(vec![Action::Resize(resize, direction)]), CliAction::Resize { resize, direction } => Ok(vec![Action::Resize(resize, direction)]),
CliAction::FocusNextPane => Ok(vec![Action::FocusNextPane]), CliAction::FocusNextPane => Ok(vec![Action::FocusNextPane]),

View file

@ -1,14 +1,14 @@
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use super::actions::Action; use super::actions::Action;
use crate::data::{InputMode, Key, KeybindsVec}; use crate::data::{InputMode, KeyWithModifier, KeybindsVec};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
/// Used in the config struct /// Used in the config struct
#[derive(Clone, PartialEq, Deserialize, Serialize, Default)] #[derive(Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct Keybinds(pub HashMap<InputMode, HashMap<Key, Vec<Action>>>); pub struct Keybinds(pub HashMap<InputMode, HashMap<KeyWithModifier, Vec<Action>>>);
impl fmt::Debug for Keybinds { impl fmt::Debug for Keybinds {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -25,7 +25,11 @@ impl fmt::Debug for Keybinds {
} }
impl Keybinds { impl Keybinds {
pub fn get_actions_for_key_in_mode(&self, mode: &InputMode, key: &Key) -> Option<&Vec<Action>> { pub fn get_actions_for_key_in_mode(
&self,
mode: &InputMode,
key: &KeyWithModifier,
) -> Option<&Vec<Action>> {
self.0 self.0
.get(mode) .get(mode)
.and_then(|normal_mode_keybindings| normal_mode_keybindings.get(key)) .and_then(|normal_mode_keybindings| normal_mode_keybindings.get(key))
@ -33,21 +37,40 @@ impl Keybinds {
pub fn get_actions_for_key_in_mode_or_default_action( pub fn get_actions_for_key_in_mode_or_default_action(
&self, &self,
mode: &InputMode, mode: &InputMode,
key: &Key, key_with_modifier: &KeyWithModifier,
raw_bytes: Vec<u8>, raw_bytes: Vec<u8>,
key_is_kitty_protocol: bool,
) -> Vec<Action> { ) -> Vec<Action> {
self.0 self.0
.get(mode) .get(mode)
.and_then(|normal_mode_keybindings| normal_mode_keybindings.get(key)) .and_then(|normal_mode_keybindings| normal_mode_keybindings.get(key_with_modifier))
.cloned() .cloned()
.unwrap_or_else(|| vec![self.default_action_for_mode(mode, raw_bytes)]) .unwrap_or_else(|| {
vec![self.default_action_for_mode(
mode,
Some(key_with_modifier),
raw_bytes,
key_is_kitty_protocol,
)]
})
} }
pub fn get_input_mode_mut(&mut self, input_mode: &InputMode) -> &mut HashMap<Key, Vec<Action>> { pub fn get_input_mode_mut(
&mut self,
input_mode: &InputMode,
) -> &mut HashMap<KeyWithModifier, Vec<Action>> {
self.0.entry(*input_mode).or_insert_with(HashMap::new) self.0.entry(*input_mode).or_insert_with(HashMap::new)
} }
pub fn default_action_for_mode(&self, mode: &InputMode, raw_bytes: Vec<u8>) -> Action { pub fn default_action_for_mode(
&self,
mode: &InputMode,
key_with_modifier: Option<&KeyWithModifier>,
raw_bytes: Vec<u8>,
key_is_kitty_protocol: bool,
) -> Action {
match *mode { match *mode {
InputMode::Normal | InputMode::Locked => Action::Write(raw_bytes), InputMode::Normal | InputMode::Locked => {
Action::Write(key_with_modifier.cloned(), raw_bytes, key_is_kitty_protocol)
},
InputMode::RenameTab => Action::TabNameInput(raw_bytes), InputMode::RenameTab => Action::TabNameInput(raw_bytes),
InputMode::RenamePane => Action::PaneNameInput(raw_bytes), InputMode::RenamePane => Action::PaneNameInput(raw_bytes),
InputMode::EnterSearch => Action::SearchInput(raw_bytes), InputMode::EnterSearch => Action::SearchInput(raw_bytes),
@ -57,7 +80,7 @@ impl Keybinds {
pub fn to_keybinds_vec(&self) -> KeybindsVec { pub fn to_keybinds_vec(&self) -> KeybindsVec {
let mut ret = vec![]; let mut ret = vec![];
for (mode, mode_binds) in &self.0 { for (mode, mode_binds) in &self.0 {
let mut mode_binds_vec: Vec<(Key, Vec<Action>)> = vec![]; let mut mode_binds_vec: Vec<(KeyWithModifier, Vec<Action>)> = vec![];
for (key, actions) in mode_binds { for (key, actions) in mode_binds {
mode_binds_vec.push((key.clone(), actions.clone())); mode_binds_vec.push((key.clone(), actions.clone()));
} }

View file

@ -18,13 +18,14 @@ pub use not_wasm::*;
#[cfg(not(target_family = "wasm"))] #[cfg(not(target_family = "wasm"))]
mod not_wasm { mod not_wasm {
use crate::{ use crate::{
data::{CharOrArrow, Direction, InputMode, Key, ModeInfo, PluginCapabilities}, data::{BareKey, InputMode, KeyModifier, KeyWithModifier, ModeInfo, PluginCapabilities},
envs, envs,
ipc::ClientAttributes, ipc::ClientAttributes,
}; };
use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers}; use termwiz::input::{InputEvent, InputParser, KeyCode, KeyEvent, Modifiers};
use super::keybinds::Keybinds; use super::keybinds::Keybinds;
use std::collections::BTreeSet;
/// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds /// Creates a [`ModeInfo`] struct indicating the current [`InputMode`] and its keybinds
/// (as pairs of [`String`]s). /// (as pairs of [`String`]s).
@ -45,7 +46,8 @@ mod not_wasm {
} }
} }
pub fn parse_keys(input_bytes: &[u8]) -> Vec<Key> { // used for parsing keys to plugins
pub fn parse_keys(input_bytes: &[u8]) -> Vec<KeyWithModifier> {
let mut ret = vec![]; let mut ret = vec![];
let mut input_parser = InputParser::new(); // this is the termwiz InputParser let mut input_parser = InputParser::new(); // this is the termwiz InputParser
let maybe_more = false; let maybe_more = false;
@ -58,9 +60,9 @@ mod not_wasm {
ret ret
} }
fn key_is_bound(key: Key, keybinds: &Keybinds, mode: &InputMode) -> bool { fn key_is_bound(key: &KeyWithModifier, keybinds: &Keybinds, mode: &InputMode) -> bool {
keybinds keybinds
.get_actions_for_key_in_mode(mode, &key) .get_actions_for_key_in_mode(mode, key)
.map_or(false, |actions| !actions.is_empty()) .map_or(false, |actions| !actions.is_empty())
} }
@ -70,81 +72,68 @@ mod not_wasm {
event: KeyEvent, event: KeyEvent,
raw_bytes: &[u8], raw_bytes: &[u8],
keybinds_mode: Option<(&Keybinds, &InputMode)>, keybinds_mode: Option<(&Keybinds, &InputMode)>,
) -> Key { ) -> KeyWithModifier {
let modifiers = event.modifiers; let termwiz_modifiers = event.modifiers;
// *** THIS IS WHERE WE SHOULD WORK AROUND ISSUES WITH TERMWIZ *** // *** THIS IS WHERE WE SHOULD WORK AROUND ISSUES WITH TERMWIZ ***
if raw_bytes == [8] { if raw_bytes == [8] {
return Key::Ctrl('h'); return KeyWithModifier::new(BareKey::Char('h')).with_ctrl_modifier();
}; };
if raw_bytes == [10] { if raw_bytes == [10] {
if let Some((keybinds, mode)) = keybinds_mode { if let Some((keybinds, mode)) = keybinds_mode {
if key_is_bound(Key::Ctrl('j'), keybinds, mode) { let ctrl_j = KeyWithModifier::new(BareKey::Char('j')).with_ctrl_modifier();
return Key::Ctrl('j'); if key_is_bound(&ctrl_j, keybinds, mode) {
return ctrl_j;
} }
} }
} }
let mut modifiers = BTreeSet::new();
if termwiz_modifiers.contains(Modifiers::CTRL) {
modifiers.insert(KeyModifier::Ctrl);
}
if termwiz_modifiers.contains(Modifiers::ALT) {
modifiers.insert(KeyModifier::Alt);
}
if termwiz_modifiers.contains(Modifiers::SHIFT) {
modifiers.insert(KeyModifier::Shift);
}
match event.key { match event.key {
KeyCode::Char(c) => { KeyCode::Char(c) => {
if modifiers.contains(Modifiers::CTRL) { if c == '\0' {
Key::Ctrl(c.to_lowercase().next().unwrap_or_default()) // NUL character, probably ctrl-space
} else if modifiers.contains(Modifiers::ALT) { KeyWithModifier::new(BareKey::Char(' ')).with_ctrl_modifier()
Key::Alt(CharOrArrow::Char(c))
} else { } else {
Key::Char(c) KeyWithModifier::new_with_modifiers(BareKey::Char(c), modifiers)
} }
}, },
KeyCode::Backspace => Key::Backspace, KeyCode::Backspace => {
KeyWithModifier::new_with_modifiers(BareKey::Backspace, modifiers)
},
KeyCode::LeftArrow | KeyCode::ApplicationLeftArrow => { KeyCode::LeftArrow | KeyCode::ApplicationLeftArrow => {
if modifiers.contains(Modifiers::ALT) { KeyWithModifier::new_with_modifiers(BareKey::Left, modifiers)
Key::Alt(CharOrArrow::Direction(Direction::Left))
} else {
Key::Left
}
}, },
KeyCode::RightArrow | KeyCode::ApplicationRightArrow => { KeyCode::RightArrow | KeyCode::ApplicationRightArrow => {
if modifiers.contains(Modifiers::ALT) { KeyWithModifier::new_with_modifiers(BareKey::Right, modifiers)
Key::Alt(CharOrArrow::Direction(Direction::Right))
} else {
Key::Right
}
}, },
KeyCode::UpArrow | KeyCode::ApplicationUpArrow => { KeyCode::UpArrow | KeyCode::ApplicationUpArrow => {
if modifiers.contains(Modifiers::ALT) { KeyWithModifier::new_with_modifiers(BareKey::Up, modifiers)
//Key::AltPlusUpArrow
Key::Alt(CharOrArrow::Direction(Direction::Up))
} else {
Key::Up
}
}, },
KeyCode::DownArrow | KeyCode::ApplicationDownArrow => { KeyCode::DownArrow | KeyCode::ApplicationDownArrow => {
if modifiers.contains(Modifiers::ALT) { KeyWithModifier::new_with_modifiers(BareKey::Down, modifiers)
Key::Alt(CharOrArrow::Direction(Direction::Down))
} else {
Key::Down
}
}, },
KeyCode::Home => Key::Home, KeyCode::Home => KeyWithModifier::new_with_modifiers(BareKey::Home, modifiers),
KeyCode::End => Key::End, KeyCode::End => KeyWithModifier::new_with_modifiers(BareKey::End, modifiers),
KeyCode::PageUp => Key::PageUp, KeyCode::PageUp => KeyWithModifier::new_with_modifiers(BareKey::PageUp, modifiers),
KeyCode::PageDown => Key::PageDown, KeyCode::PageDown => KeyWithModifier::new_with_modifiers(BareKey::PageDown, modifiers),
KeyCode::Tab => Key::BackTab, // TODO: ??? KeyCode::Tab => KeyWithModifier::new_with_modifiers(BareKey::Tab, modifiers),
KeyCode::Delete => Key::Delete, KeyCode::Delete => KeyWithModifier::new_with_modifiers(BareKey::Delete, modifiers),
KeyCode::Insert => Key::Insert, KeyCode::Insert => KeyWithModifier::new_with_modifiers(BareKey::Insert, modifiers),
KeyCode::Function(n) => { KeyCode::Function(n) => KeyWithModifier::new_with_modifiers(BareKey::F(n), modifiers),
if modifiers.contains(Modifiers::ALT) { KeyCode::Escape => KeyWithModifier::new_with_modifiers(BareKey::Esc, modifiers),
Key::AltF(n) KeyCode::Enter => KeyWithModifier::new_with_modifiers(BareKey::Enter, modifiers),
} else if modifiers.contains(Modifiers::CTRL) { _ => KeyWithModifier::new(BareKey::Esc),
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
} }
} }
} }

View file

@ -155,6 +155,12 @@ pub struct Options {
/// If true, will disable writing session metadata to disk /// If true, will disable writing session metadata to disk
#[clap(long, value_parser)] #[clap(long, value_parser)]
pub disable_session_metadata: Option<bool>, pub disable_session_metadata: Option<bool>,
/// Whether to enable support for the Kitty keyboard protocol (must also be supported by the
/// host terminal), defaults to true if the terminal supports it
#[clap(long, value_parser)]
#[serde(default)]
pub support_kitty_keyboard_protocol: Option<bool>,
} }
#[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] #[derive(ArgEnum, Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
@ -230,6 +236,9 @@ impl Options {
let disable_session_metadata = other let disable_session_metadata = other
.disable_session_metadata .disable_session_metadata
.or(self.disable_session_metadata); .or(self.disable_session_metadata);
let support_kitty_keyboard_protocol = other
.support_kitty_keyboard_protocol
.or(self.support_kitty_keyboard_protocol);
Options { Options {
simplified_ui, simplified_ui,
@ -258,6 +267,7 @@ impl Options {
styled_underlines, styled_underlines,
serialization_interval, serialization_interval,
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol,
} }
} }
@ -313,6 +323,9 @@ impl Options {
let disable_session_metadata = other let disable_session_metadata = other
.disable_session_metadata .disable_session_metadata
.or(self.disable_session_metadata); .or(self.disable_session_metadata);
let support_kitty_keyboard_protocol = other
.support_kitty_keyboard_protocol
.or(self.support_kitty_keyboard_protocol);
Options { Options {
simplified_ui, simplified_ui,
@ -341,6 +354,7 @@ impl Options {
styled_underlines, styled_underlines,
serialization_interval, serialization_interval,
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol,
} }
} }
@ -405,6 +419,7 @@ impl From<CliOptions> for Options {
scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize, scrollback_lines_to_serialize: opts.scrollback_lines_to_serialize,
styled_underlines: opts.styled_underlines, styled_underlines: opts.styled_underlines,
serialization_interval: opts.serialization_interval, serialization_interval: opts.serialization_interval,
support_kitty_keyboard_protocol: opts.support_kitty_keyboard_protocol,
..Default::default() ..Default::default()
} }
} }

View file

@ -1,6 +1,6 @@
use super::super::actions::*; use super::super::actions::*;
use super::super::keybinds::*; use super::super::keybinds::*;
use crate::data::{self, CharOrArrow, Direction, Key}; use crate::data::{BareKey, Direction, KeyWithModifier};
use crate::input::config::Config; use crate::input::config::Config;
use insta::assert_snapshot; use insta::assert_snapshot;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -15,9 +15,10 @@ fn can_define_keybindings_in_configfile() {
} }
"#; "#;
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let ctrl_g_normal_mode_action = config let ctrl_g_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
assert_eq!( assert_eq!(
ctrl_g_normal_mode_action, ctrl_g_normal_mode_action,
Some(&vec![Action::SwitchToMode(InputMode::Locked)]), Some(&vec![Action::SwitchToMode(InputMode::Locked)]),
@ -37,11 +38,12 @@ fn can_define_multiple_keybinds_for_same_action() {
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let alt_h_normal_mode_action = config.keybinds.get_actions_for_key_in_mode( let alt_h_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
&InputMode::Normal, &InputMode::Normal,
&Key::Alt(CharOrArrow::Direction(data::Direction::Left)), &KeyWithModifier::new(BareKey::Left).with_alt_modifier(),
);
let alt_left_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
&InputMode::Normal,
&KeyWithModifier::new(BareKey::Char('h')).with_alt_modifier(),
); );
let alt_left_normal_mode_action = config
.keybinds
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Alt(CharOrArrow::Char('h')));
assert_eq!( assert_eq!(
alt_h_normal_mode_action, alt_h_normal_mode_action,
Some(&vec![Action::MoveFocusOrTab(Direction::Left)]), Some(&vec![Action::MoveFocusOrTab(Direction::Left)]),
@ -66,7 +68,7 @@ fn can_define_series_of_actions_for_same_keybinding() {
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
assert_eq!( assert_eq!(
z_in_pane_mode, z_in_pane_mode,
Some(&vec![ Some(&vec![
@ -90,7 +92,7 @@ fn keybindings_bind_order_is_preserved() {
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
assert_eq!( assert_eq!(
z_in_pane_mode, z_in_pane_mode,
Some(&vec![Action::SwitchToMode(InputMode::Resize)]), Some(&vec![Action::SwitchToMode(InputMode::Resize)]),
@ -111,10 +113,10 @@ fn uppercase_and_lowercase_keybindings_are_distinct() {
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let uppercase_z_in_pane_mode = config let uppercase_z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('Z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('Z')));
assert_eq!( assert_eq!(
z_in_pane_mode, z_in_pane_mode,
Some(&vec![ Some(&vec![
@ -150,7 +152,7 @@ fn can_override_keybindings() {
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
assert_eq!( assert_eq!(
z_in_pane_mode, z_in_pane_mode,
Some(&vec![Action::SwitchToMode(InputMode::Resize)]), Some(&vec![Action::SwitchToMode(InputMode::Resize)]),
@ -180,10 +182,10 @@ fn can_add_to_default_keybindings() {
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let r_in_pane_mode = config let r_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('r')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('r')));
assert_eq!( assert_eq!(
z_in_pane_mode, z_in_pane_mode,
Some(&vec![ Some(&vec![
@ -223,18 +225,20 @@ fn can_clear_default_keybindings() {
"#; "#;
let default_config = Config::from_kdl(default_config_contents, None).unwrap(); let default_config = Config::from_kdl(default_config_contents, None).unwrap();
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let ctrl_g_normal_mode_action = config let ctrl_g_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let ctrl_r_in_normal_mode = config let ctrl_r_in_normal_mode = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('r')); &KeyWithModifier::new(BareKey::Char('r')).with_ctrl_modifier(),
);
let r_in_pane_mode = config let r_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('r')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('r')));
assert_eq!( assert_eq!(
ctrl_g_normal_mode_action, None, ctrl_g_normal_mode_action, None,
"Keybinding from normal mode in default config cleared" "Keybinding from normal mode in default config cleared"
@ -276,15 +280,16 @@ fn can_clear_default_keybindings_per_single_mode() {
"#; "#;
let default_config = Config::from_kdl(default_config_contents, None).unwrap(); let default_config = Config::from_kdl(default_config_contents, None).unwrap();
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let ctrl_g_normal_mode_action = config let ctrl_g_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let r_in_pane_mode = config let r_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('r')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('r')));
assert_eq!( assert_eq!(
ctrl_g_normal_mode_action, ctrl_g_normal_mode_action,
Some(&vec![Action::SwitchToMode(InputMode::Locked)]), Some(&vec![Action::SwitchToMode(InputMode::Locked)]),
@ -325,21 +330,23 @@ fn can_unbind_multiple_keys_globally() {
"#; "#;
let default_config = Config::from_kdl(default_config_contents, None).unwrap(); let default_config = Config::from_kdl(default_config_contents, None).unwrap();
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let ctrl_g_normal_mode_action = config let ctrl_g_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
let ctrl_g_pane_mode_action = config );
.keybinds let ctrl_g_pane_mode_action = config.keybinds.get_actions_for_key_in_mode(
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Ctrl('g')); &InputMode::Pane,
&KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
let r_in_pane_mode = config let r_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('r')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('r')));
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let t_in_pane_mode = config let t_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('t')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('t')));
assert_eq!( assert_eq!(
ctrl_g_normal_mode_action, None, ctrl_g_normal_mode_action, None,
"First keybind uncleared in one mode" "First keybind uncleared in one mode"
@ -385,21 +392,23 @@ fn can_unbind_multiple_keys_per_single_mode() {
"#; "#;
let default_config = Config::from_kdl(default_config_contents, None).unwrap(); let default_config = Config::from_kdl(default_config_contents, None).unwrap();
let config = Config::from_kdl(config_contents, Some(default_config)).unwrap(); let config = Config::from_kdl(config_contents, Some(default_config)).unwrap();
let ctrl_g_normal_mode_action = config let ctrl_g_normal_mode_action = config.keybinds.get_actions_for_key_in_mode(
.keybinds &InputMode::Normal,
.get_actions_for_key_in_mode(&InputMode::Normal, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
let ctrl_g_pane_mode_action = config );
.keybinds let ctrl_g_pane_mode_action = config.keybinds.get_actions_for_key_in_mode(
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Ctrl('g')); &InputMode::Pane,
&KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
let r_in_pane_mode = config let r_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('r')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('r')));
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
let t_in_pane_mode = config let t_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('t')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('t')));
assert_eq!( assert_eq!(
ctrl_g_normal_mode_action, ctrl_g_normal_mode_action,
Some(&vec![Action::SwitchToMode(InputMode::Locked)]), Some(&vec![Action::SwitchToMode(InputMode::Locked)]),
@ -436,9 +445,10 @@ fn can_define_shared_keybinds_for_all_modes() {
"#; "#;
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
for mode in InputMode::iter() { for mode in InputMode::iter() {
let action_in_mode = config let action_in_mode = config.keybinds.get_actions_for_key_in_mode(
.keybinds &mode,
.get_actions_for_key_in_mode(&mode, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
assert_eq!( assert_eq!(
action_in_mode, action_in_mode,
Some(&vec![Action::SwitchToMode(InputMode::Locked)]), Some(&vec![Action::SwitchToMode(InputMode::Locked)]),
@ -458,9 +468,10 @@ fn can_define_shared_keybinds_with_exclusion() {
"#; "#;
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
for mode in InputMode::iter() { for mode in InputMode::iter() {
let action_in_mode = config let action_in_mode = config.keybinds.get_actions_for_key_in_mode(
.keybinds &mode,
.get_actions_for_key_in_mode(&mode, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
if mode == InputMode::Locked { if mode == InputMode::Locked {
assert_eq!(action_in_mode, None, "Keybind unbound in excluded mode"); assert_eq!(action_in_mode, None, "Keybind unbound in excluded mode");
} else { } else {
@ -484,9 +495,10 @@ fn can_define_shared_keybinds_with_inclusion() {
"#; "#;
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
for mode in InputMode::iter() { for mode in InputMode::iter() {
let action_in_mode = config let action_in_mode = config.keybinds.get_actions_for_key_in_mode(
.keybinds &mode,
.get_actions_for_key_in_mode(&mode, &Key::Ctrl('g')); &KeyWithModifier::new(BareKey::Char('g')).with_ctrl_modifier(),
);
if mode == InputMode::Normal || mode == InputMode::Resize || mode == InputMode::Pane { if mode == InputMode::Normal || mode == InputMode::Resize || mode == InputMode::Pane {
assert_eq!( assert_eq!(
action_in_mode, action_in_mode,
@ -512,7 +524,7 @@ fn keybindings_unbinds_happen_after_binds() {
let config = Config::from_kdl(config_contents, None).unwrap(); let config = Config::from_kdl(config_contents, None).unwrap();
let z_in_pane_mode = config let z_in_pane_mode = config
.keybinds .keybinds
.get_actions_for_key_in_mode(&InputMode::Pane, &Key::Char('z')); .get_actions_for_key_in_mode(&InputMode::Pane, &KeyWithModifier::new(BareKey::Char('z')));
assert_eq!(z_in_pane_mode, None, "Key was ultimately unbound"); assert_eq!(z_in_pane_mode, None, "Key was ultimately unbound");
} }

View file

@ -1,7 +1,7 @@
mod kdl_layout_parser; mod kdl_layout_parser;
use crate::data::{ use crate::data::{
Direction, FloatingPaneCoordinates, InputMode, Key, LayoutInfo, Palette, PaletteColor, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo, Palette,
PaneInfo, PaneManifest, PermissionType, Resize, SessionInfo, TabInfo, PaletteColor, PaneInfo, PaneManifest, PermissionType, Resize, SessionInfo, TabInfo,
}; };
use crate::envs::EnvironmentVariables; use crate::envs::EnvironmentVariables;
use crate::home::{find_default_config_dir, get_layout_dir}; use crate::home::{find_default_config_dir, get_layout_dir};
@ -320,7 +320,7 @@ macro_rules! keys_from_kdl {
kdl_string_arguments!($kdl_node) kdl_string_arguments!($kdl_node)
.iter() .iter()
.map(|k| { .map(|k| {
Key::from_str(k).map_err(|_| { KeyWithModifier::from_str(k).map_err(|_| {
ConfigError::new_kdl_error( ConfigError::new_kdl_error(
format!("Invalid key: '{}'", k), format!("Invalid key: '{}'", k),
$kdl_node.span().offset(), $kdl_node.span().offset(),
@ -388,7 +388,7 @@ impl Action {
action_node: &KdlNode, action_node: &KdlNode,
) -> Result<Self, ConfigError> { ) -> Result<Self, ConfigError> {
match action_name { match action_name {
"Write" => Ok(Action::Write(bytes)), "Write" => Ok(Action::Write(None, bytes, false)),
"PaneNameInput" => Ok(Action::PaneNameInput(bytes)), "PaneNameInput" => Ok(Action::PaneNameInput(bytes)),
"TabNameInput" => Ok(Action::TabNameInput(bytes)), "TabNameInput" => Ok(Action::TabNameInput(bytes)),
"SearchInput" => Ok(Action::SearchInput(bytes)), "SearchInput" => Ok(Action::SearchInput(bytes)),
@ -1614,6 +1614,11 @@ impl Options {
let disable_session_metadata = let disable_session_metadata =
kdl_property_first_arg_as_bool_or_error!(kdl_options, "disable_session_metadata") kdl_property_first_arg_as_bool_or_error!(kdl_options, "disable_session_metadata")
.map(|(v, _)| v); .map(|(v, _)| v);
let support_kitty_keyboard_protocol = kdl_property_first_arg_as_bool_or_error!(
kdl_options,
"support_kitty_keyboard_protocol"
)
.map(|(v, _)| v);
Ok(Options { Ok(Options {
simplified_ui, simplified_ui,
theme, theme,
@ -1641,6 +1646,7 @@ impl Options {
styled_underlines, styled_underlines,
serialization_interval, serialization_interval,
disable_session_metadata, disable_session_metadata,
support_kitty_keyboard_protocol,
}) })
} }
} }
@ -1735,7 +1741,7 @@ impl EnvironmentVariables {
impl Keybinds { impl Keybinds {
fn bind_keys_in_block( fn bind_keys_in_block(
block: &KdlNode, block: &KdlNode,
input_mode_keybinds: &mut HashMap<Key, Vec<Action>>, input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
config_options: &Options, config_options: &Options,
) -> Result<(), ConfigError> { ) -> Result<(), ConfigError> {
let all_nodes = kdl_children_nodes_or_error!(block, "no keybinding block for mode"); let all_nodes = kdl_children_nodes_or_error!(block, "no keybinding block for mode");
@ -1823,10 +1829,10 @@ impl Keybinds {
} }
fn bind_actions_for_each_key( fn bind_actions_for_each_key(
key_block: &KdlNode, key_block: &KdlNode,
input_mode_keybinds: &mut HashMap<Key, Vec<Action>>, input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
config_options: &Options, config_options: &Options,
) -> Result<(), ConfigError> { ) -> Result<(), ConfigError> {
let keys: Vec<Key> = keys_from_kdl!(key_block); let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
let actions: Vec<Action> = actions_from_kdl!(key_block, config_options); let actions: Vec<Action> = actions_from_kdl!(key_block, config_options);
for key in keys { for key in keys {
input_mode_keybinds.insert(key, actions.clone()); input_mode_keybinds.insert(key, actions.clone());
@ -1835,9 +1841,9 @@ impl Keybinds {
} }
fn unbind_keys( fn unbind_keys(
key_block: &KdlNode, key_block: &KdlNode,
input_mode_keybinds: &mut HashMap<Key, Vec<Action>>, input_mode_keybinds: &mut HashMap<KeyWithModifier, Vec<Action>>,
) -> Result<(), ConfigError> { ) -> Result<(), ConfigError> {
let keys: Vec<Key> = keys_from_kdl!(key_block); let keys: Vec<KeyWithModifier> = keys_from_kdl!(key_block);
for key in keys { for key in keys {
input_mode_keybinds.remove(&key); input_mode_keybinds.remove(&key);
} }
@ -1847,7 +1853,7 @@ impl Keybinds {
global_unbind: &KdlNode, global_unbind: &KdlNode,
keybinds_from_config: &mut Keybinds, keybinds_from_config: &mut Keybinds,
) -> Result<(), ConfigError> { ) -> Result<(), ConfigError> {
let keys: Vec<Key> = keys_from_kdl!(global_unbind); let keys: Vec<KeyWithModifier> = keys_from_kdl!(global_unbind);
for mode in keybinds_from_config.0.values_mut() { for mode in keybinds_from_config.0.values_mut() {
for key in &keys { for key in &keys {
mode.remove(&key); mode.remove(&key);
@ -1858,7 +1864,7 @@ impl Keybinds {
fn input_mode_keybindings<'a>( fn input_mode_keybindings<'a>(
mode: &KdlNode, mode: &KdlNode,
keybinds_from_config: &'a mut Keybinds, keybinds_from_config: &'a mut Keybinds,
) -> Result<&'a mut HashMap<Key, Vec<Action>>, ConfigError> { ) -> Result<&'a mut HashMap<KeyWithModifier, Vec<Action>>, ConfigError> {
let mode_name = kdl_name!(mode); let mode_name = kdl_name!(mode);
let input_mode = InputMode::from_str(mode_name).map_err(|_| { let input_mode = InputMode::from_str(mode_name).map_err(|_| {
ConfigError::new_kdl_error( ConfigError::new_kdl_error(

View file

@ -37,7 +37,7 @@ impl TryFrom<ProtobufAction> for Action {
}, },
Some(ProtobufActionName::Write) => match protobuf_action.optional_payload { Some(ProtobufActionName::Write) => match protobuf_action.optional_payload {
Some(OptionalPayload::WritePayload(write_payload)) => { Some(OptionalPayload::WritePayload(write_payload)) => {
Ok(Action::Write(write_payload.bytes_to_write)) Ok(Action::Write(None, write_payload.bytes_to_write, false))
}, },
_ => Err("Wrong payload for Action::Write"), _ => Err("Wrong payload for Action::Write"),
}, },
@ -719,7 +719,7 @@ impl TryFrom<Action> for ProtobufAction {
name: ProtobufActionName::Quit as i32, name: ProtobufActionName::Quit as i32,
optional_payload: None, optional_payload: None,
}), }),
Action::Write(bytes) => Ok(ProtobufAction { Action::Write(_, bytes, _) => Ok(ProtobufAction {
name: ProtobufActionName::Write as i32, name: ProtobufActionName::Write as i32,
optional_payload: Some(OptionalPayload::WritePayload(WritePayload { optional_payload: Some(OptionalPayload::WritePayload(WritePayload {
bytes_to_write: bytes, bytes_to_write: bytes,

View file

@ -16,8 +16,9 @@ pub use super::generated_api::api::{
}; };
#[allow(hidden_glob_reexports)] #[allow(hidden_glob_reexports)]
use crate::data::{ use crate::data::{
CopyDestination, Event, EventType, FileMetadata, InputMode, Key, LayoutInfo, ModeInfo, Mouse, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, LayoutInfo,
PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities, SessionInfo, Style, TabInfo, ModeInfo, Mouse, PaneInfo, PaneManifest, PermissionStatus, PluginCapabilities, SessionInfo,
Style, TabInfo,
}; };
use crate::errors::prelude::*; use crate::errors::prelude::*;
@ -812,29 +813,30 @@ impl TryFrom<ProtobufModeUpdatePayload> for ModeInfo {
ProtobufInputMode::from_i32(protobuf_mode_update_payload.current_mode) ProtobufInputMode::from_i32(protobuf_mode_update_payload.current_mode)
.ok_or("Malformed InputMode in the ModeUpdate Event")? .ok_or("Malformed InputMode in the ModeUpdate Event")?
.try_into()?; .try_into()?;
let keybinds: Vec<(InputMode, Vec<(Key, Vec<Action>)>)> = protobuf_mode_update_payload let keybinds: Vec<(InputMode, Vec<(KeyWithModifier, Vec<Action>)>)> =
.keybinds protobuf_mode_update_payload
.iter_mut() .keybinds
.filter_map(|k| { .iter_mut()
let input_mode: InputMode = ProtobufInputMode::from_i32(k.mode) .filter_map(|k| {
.ok_or("Malformed InputMode in the ModeUpdate Event") let input_mode: InputMode = ProtobufInputMode::from_i32(k.mode)
.ok()? .ok_or("Malformed InputMode in the ModeUpdate Event")
.try_into() .ok()?
.ok()?; .try_into()
let mut keybinds: Vec<(Key, Vec<Action>)> = vec![]; .ok()?;
for mut protobuf_keybind in k.key_bind.drain(..) { let mut keybinds: Vec<(KeyWithModifier, Vec<Action>)> = vec![];
let key: Key = protobuf_keybind.key.unwrap().try_into().ok()?; for mut protobuf_keybind in k.key_bind.drain(..) {
let mut actions: Vec<Action> = vec![]; let key: KeyWithModifier = protobuf_keybind.key.unwrap().try_into().ok()?;
for action in protobuf_keybind.action.drain(..) { let mut actions: Vec<Action> = vec![];
if let Ok(action) = action.try_into() { for action in protobuf_keybind.action.drain(..) {
actions.push(action); if let Ok(action) = action.try_into() {
actions.push(action);
}
} }
keybinds.push((key, actions));
} }
keybinds.push((key, actions)); Some((input_mode, keybinds))
} })
Some((input_mode, keybinds)) .collect();
})
.collect();
let style: Style = protobuf_mode_update_payload let style: Style = protobuf_mode_update_payload
.style .style
.and_then(|m| m.try_into().ok()) .and_then(|m| m.try_into().ok())
@ -1047,7 +1049,7 @@ fn serialize_mode_update_event() {
#[test] #[test]
fn serialize_mode_update_event_with_non_default_values() { fn serialize_mode_update_event_with_non_default_values() {
use crate::data::{Direction, Palette, PaletteColor, ThemeHue}; use crate::data::{BareKey, Palette, PaletteColor, ThemeHue};
use prost::Message; use prost::Message;
let mode_update_event = Event::ModeUpdate(ModeInfo { let mode_update_event = Event::ModeUpdate(ModeInfo {
mode: InputMode::Locked, mode: InputMode::Locked,
@ -1055,14 +1057,14 @@ fn serialize_mode_update_event_with_non_default_values() {
( (
InputMode::Locked, InputMode::Locked,
vec![( vec![(
Key::Alt(crate::data::CharOrArrow::Char('b')), KeyWithModifier::new(BareKey::Char('b')).with_alt_modifier(),
vec![Action::SwitchToMode(InputMode::Normal)], vec![Action::SwitchToMode(InputMode::Normal)],
)], )],
), ),
( (
InputMode::Tab, InputMode::Tab,
vec![( vec![(
Key::Alt(crate::data::CharOrArrow::Direction(Direction::Up)), KeyWithModifier::new(BareKey::Up).with_alt_modifier(),
vec![Action::SwitchToMode(InputMode::Pane)], vec![Action::SwitchToMode(InputMode::Pane)],
)], )],
), ),
@ -1070,13 +1072,16 @@ fn serialize_mode_update_event_with_non_default_values() {
InputMode::Pane, InputMode::Pane,
vec![ vec![
( (
Key::Ctrl('b'), KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(),
vec![ vec![
Action::SwitchToMode(InputMode::Tmux), Action::SwitchToMode(InputMode::Tmux),
Action::Write(vec![10]), Action::Write(None, vec![10], false),
], ],
), ),
(Key::Char('a'), vec![Action::WriteChars("foo".to_owned())]), (
KeyWithModifier::new(BareKey::Char('a')),
vec![Action::WriteChars("foo".to_owned())],
),
], ],
), ),
], ],
@ -1192,8 +1197,9 @@ fn serialize_pane_update_event() {
#[test] #[test]
fn serialize_key_event() { fn serialize_key_event() {
use crate::data::BareKey;
use prost::Message; use prost::Message;
let key_event = Event::Key(Key::Ctrl('a')); let key_event = Event::Key(KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier());
let protobuf_event: ProtobufEvent = key_event.clone().try_into().unwrap(); let protobuf_event: ProtobufEvent = key_event.clone().try_into().unwrap();
let serialized_protobuf_event = protobuf_event.encode_to_vec(); let serialized_protobuf_event = protobuf_event.encode_to_vec();
let deserialized_protobuf_event: ProtobufEvent = let deserialized_protobuf_event: ProtobufEvent =

View file

@ -6,6 +6,8 @@ message Key {
enum KeyModifier { enum KeyModifier {
CTRL = 0; CTRL = 0;
ALT = 1; ALT = 1;
SHIFT = 2;
SUPER = 3;
} }
enum NamedKey { enum NamedKey {
@ -34,6 +36,13 @@ message Key {
F12 = 22; F12 = 22;
Tab = 23; Tab = 23;
Esc = 24; Esc = 24;
CapsLock = 25;
ScrollLock = 26;
NumLock = 27;
PrintScreen = 28;
Pause = 29;
Menu = 30;
Enter = 31;
} }
enum Char { enum Char {
@ -80,4 +89,5 @@ message Key {
NamedKey key = 2; NamedKey key = 2;
Char char = 3; Char char = 3;
} }
repeated KeyModifier additional_modifiers = 4;
} }

View file

@ -1,258 +1,184 @@
pub use super::generated_api::api::key::{ pub use super::generated_api::api::key::{
key::{KeyModifier, MainKey, NamedKey}, key::{
KeyModifier as ProtobufKeyModifier, MainKey as ProtobufMainKey,
NamedKey as ProtobufNamedKey,
},
Key as ProtobufKey, Key as ProtobufKey,
}; };
use crate::data::{CharOrArrow, Direction, Key}; use crate::data::{BareKey, KeyModifier, KeyWithModifier};
use std::collections::BTreeSet;
use std::convert::TryFrom; use std::convert::TryFrom;
impl TryFrom<ProtobufKey> for Key { impl TryFrom<ProtobufMainKey> for BareKey {
type Error = &'static str;
fn try_from(protobuf_main_key: ProtobufMainKey) -> Result<Self, &'static str> {
match protobuf_main_key {
ProtobufMainKey::Char(character) => Ok(BareKey::Char(char_index_to_char(character))),
ProtobufMainKey::Key(key_index) => {
let key = ProtobufNamedKey::from_i32(key_index).ok_or("invalid_key")?;
Ok(named_key_to_bare_key(key))
},
}
}
}
impl TryFrom<BareKey> for ProtobufMainKey {
type Error = &'static str;
fn try_from(bare_key: BareKey) -> Result<Self, &'static str> {
match bare_key {
BareKey::PageDown => Ok(ProtobufMainKey::Key(ProtobufNamedKey::PageDown as i32)),
BareKey::PageUp => Ok(ProtobufMainKey::Key(ProtobufNamedKey::PageUp as i32)),
BareKey::Left => Ok(ProtobufMainKey::Key(ProtobufNamedKey::LeftArrow as i32)),
BareKey::Down => Ok(ProtobufMainKey::Key(ProtobufNamedKey::DownArrow as i32)),
BareKey::Up => Ok(ProtobufMainKey::Key(ProtobufNamedKey::UpArrow as i32)),
BareKey::Right => Ok(ProtobufMainKey::Key(ProtobufNamedKey::RightArrow as i32)),
BareKey::Home => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Home as i32)),
BareKey::End => Ok(ProtobufMainKey::Key(ProtobufNamedKey::End as i32)),
BareKey::Backspace => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Backspace as i32)),
BareKey::Delete => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Delete as i32)),
BareKey::Insert => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Insert as i32)),
BareKey::F(f_index) => fn_index_to_main_key(f_index),
BareKey::Char(character) => Ok(ProtobufMainKey::Char(character as i32)),
BareKey::Tab => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Tab as i32)),
BareKey::Esc => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Esc as i32)),
BareKey::Enter => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Enter as i32)),
BareKey::CapsLock => Ok(ProtobufMainKey::Key(ProtobufNamedKey::CapsLock as i32)),
BareKey::ScrollLock => Ok(ProtobufMainKey::Key(ProtobufNamedKey::ScrollLock as i32)),
BareKey::NumLock => Ok(ProtobufMainKey::Key(ProtobufNamedKey::NumLock as i32)),
BareKey::PrintScreen => Ok(ProtobufMainKey::Key(ProtobufNamedKey::PrintScreen as i32)),
BareKey::Pause => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Pause as i32)),
BareKey::Menu => Ok(ProtobufMainKey::Key(ProtobufNamedKey::Menu as i32)),
}
}
}
impl TryFrom<ProtobufKeyModifier> for KeyModifier {
type Error = &'static str;
fn try_from(protobuf_key_modifier: ProtobufKeyModifier) -> Result<Self, &'static str> {
match protobuf_key_modifier {
ProtobufKeyModifier::Ctrl => Ok(KeyModifier::Ctrl),
ProtobufKeyModifier::Alt => Ok(KeyModifier::Alt),
ProtobufKeyModifier::Shift => Ok(KeyModifier::Shift),
ProtobufKeyModifier::Super => Ok(KeyModifier::Super),
}
}
}
impl TryFrom<KeyModifier> for ProtobufKeyModifier {
type Error = &'static str;
fn try_from(key_modifier: KeyModifier) -> Result<Self, &'static str> {
match key_modifier {
KeyModifier::Ctrl => Ok(ProtobufKeyModifier::Ctrl),
KeyModifier::Alt => Ok(ProtobufKeyModifier::Alt),
KeyModifier::Shift => Ok(ProtobufKeyModifier::Shift),
KeyModifier::Super => Ok(ProtobufKeyModifier::Super),
_ => Err("unsupported key modifier"), // TODO: test this so we don't crash if we have a
// Capslock or something
}
}
}
impl TryFrom<ProtobufKey> for KeyWithModifier {
type Error = &'static str; type Error = &'static str;
fn try_from(protobuf_key: ProtobufKey) -> Result<Self, &'static str> { fn try_from(protobuf_key: ProtobufKey) -> Result<Self, &'static str> {
let key_modifier = parse_optional_modifier(&protobuf_key); let bare_key = protobuf_key
match key_modifier { .main_key
Some(KeyModifier::Ctrl) => { .ok_or("Key must have main_key")?
if let Ok(character) = char_from_main_key(protobuf_key.main_key.clone()) { .try_into()?;
Ok(Key::Ctrl(character)) let mut key_modifiers = BTreeSet::new();
} else { if let Some(main_modifier) = protobuf_key.modifier {
let index = fn_index_from_main_key(protobuf_key.main_key)?; key_modifiers.insert(
Ok(Key::CtrlF(index)) ProtobufKeyModifier::from_i32(main_modifier)
} .ok_or("invalid key modifier")?
}, .try_into()?,
Some(KeyModifier::Alt) => { );
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) => {
let character = char_from_main_key(protobuf_key.main_key)?;
Ok(Key::Char(character))
},
MainKey::Key(key_index) => {
let key = NamedKey::from_i32(*key_index).ok_or("invalid_key")?;
Ok(named_key_to_key(key))
},
},
} }
for key_modifier in protobuf_key.additional_modifiers {
key_modifiers.insert(
ProtobufKeyModifier::from_i32(key_modifier)
.ok_or("invalid key modifier")?
.try_into()?,
);
}
Ok(KeyWithModifier {
bare_key,
key_modifiers,
})
} }
} }
impl TryFrom<Key> for ProtobufKey { impl TryFrom<KeyWithModifier> for ProtobufKey {
type Error = &'static str; type Error = &'static str;
fn try_from(key: Key) -> Result<Self, &'static str> { fn try_from(key_with_modifier: KeyWithModifier) -> Result<Self, &'static str> {
match key { let mut modifiers: Vec<ProtobufKeyModifier> = vec![];
Key::PageDown => Ok(ProtobufKey { for key_modifier in key_with_modifier.key_modifiers {
modifier: None, modifiers.push(key_modifier.try_into()?);
main_key: Some(MainKey::Key(NamedKey::PageDown as i32)),
}),
Key::PageUp => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::PageUp as i32)),
}),
Key::Left => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::LeftArrow as i32)),
}),
Key::Down => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::DownArrow as i32)),
}),
Key::Up => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::UpArrow as i32)),
}),
Key::Right => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::RightArrow as i32)),
}),
Key::Home => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Home as i32)),
}),
Key::End => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::End as i32)),
}),
Key::Backspace => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Backspace as i32)),
}),
Key::Delete => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Delete as i32)),
}),
Key::Insert => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Insert as i32)),
}),
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)),
}),
Key::Alt(char_or_arrow) => {
let main_key = match char_or_arrow {
CharOrArrow::Char(character) => MainKey::Char((character as u8) as i32),
CharOrArrow::Direction(Direction::Left) => {
MainKey::Key(NamedKey::LeftArrow as i32)
},
CharOrArrow::Direction(Direction::Right) => {
MainKey::Key(NamedKey::RightArrow as i32)
},
CharOrArrow::Direction(Direction::Up) => MainKey::Key(NamedKey::UpArrow as i32),
CharOrArrow::Direction(Direction::Down) => {
MainKey::Key(NamedKey::DownArrow as i32)
},
};
Ok(ProtobufKey {
modifier: Some(KeyModifier::Alt as i32),
main_key: Some(main_key),
})
},
Key::Ctrl(character) => Ok(ProtobufKey {
modifier: Some(KeyModifier::Ctrl as i32),
main_key: Some(MainKey::Char((character as u8) as i32)),
}),
Key::BackTab => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Tab as i32)),
}),
Key::Null => {
Ok(ProtobufKey {
modifier: None,
main_key: None, // TODO: does this break deserialization?
})
},
Key::Esc => Ok(ProtobufKey {
modifier: None,
main_key: Some(MainKey::Key(NamedKey::Esc as i32)),
}),
} }
Ok(ProtobufKey {
main_key: Some(key_with_modifier.bare_key.try_into()?),
modifier: modifiers.pop().map(|m| m as i32),
additional_modifiers: modifiers.into_iter().map(|m| m as i32).collect(),
})
} }
} }
fn fn_index_to_main_key(index: u8) -> Result<MainKey, &'static str> { fn fn_index_to_main_key(index: u8) -> Result<ProtobufMainKey, &'static str> {
match index { match index {
1 => Ok(MainKey::Key(NamedKey::F1 as i32)), 1 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F1 as i32)),
2 => Ok(MainKey::Key(NamedKey::F2 as i32)), 2 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F2 as i32)),
3 => Ok(MainKey::Key(NamedKey::F3 as i32)), 3 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F3 as i32)),
4 => Ok(MainKey::Key(NamedKey::F4 as i32)), 4 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F4 as i32)),
5 => Ok(MainKey::Key(NamedKey::F5 as i32)), 5 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F5 as i32)),
6 => Ok(MainKey::Key(NamedKey::F6 as i32)), 6 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F6 as i32)),
7 => Ok(MainKey::Key(NamedKey::F7 as i32)), 7 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F7 as i32)),
8 => Ok(MainKey::Key(NamedKey::F8 as i32)), 8 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F8 as i32)),
9 => Ok(MainKey::Key(NamedKey::F9 as i32)), 9 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F9 as i32)),
10 => Ok(MainKey::Key(NamedKey::F10 as i32)), 10 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F10 as i32)),
11 => Ok(MainKey::Key(NamedKey::F11 as i32)), 11 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F11 as i32)),
12 => Ok(MainKey::Key(NamedKey::F12 as i32)), 12 => Ok(ProtobufMainKey::Key(ProtobufNamedKey::F12 as i32)),
_ => Err("Invalid key"), _ => Err("Invalid key"),
} }
} }
impl CharOrArrow {
pub fn from_main_key(
main_key: std::option::Option<MainKey>,
) -> Result<CharOrArrow, &'static str> {
match main_key {
Some(MainKey::Char(encoded_key)) => {
Ok(CharOrArrow::Char(char_index_to_char(encoded_key)))
},
Some(MainKey::Key(key_index)) => match NamedKey::from_i32(key_index) {
Some(NamedKey::LeftArrow) => Ok(CharOrArrow::Direction(Direction::Left)),
Some(NamedKey::RightArrow) => Ok(CharOrArrow::Direction(Direction::Right)),
Some(NamedKey::UpArrow) => Ok(CharOrArrow::Direction(Direction::Up)),
Some(NamedKey::DownArrow) => Ok(CharOrArrow::Direction(Direction::Down)),
_ => Err("Unsupported key"),
},
_ => {
return Err("Unsupported key");
},
}
}
}
fn parse_optional_modifier(m: &ProtobufKey) -> Option<KeyModifier> {
match m.modifier {
Some(modifier) => KeyModifier::from_i32(modifier),
_ => None,
}
}
fn char_index_to_char(char_index: i32) -> char { fn char_index_to_char(char_index: i32) -> char {
char_index as u8 as char char_index as u8 as char
} }
fn char_from_main_key(main_key: Option<MainKey>) -> Result<char, &'static str> { fn named_key_to_bare_key(named_key: ProtobufNamedKey) -> BareKey {
match main_key {
Some(MainKey::Char(encoded_key)) => {
return Ok(char_index_to_char(encoded_key));
},
_ => {
return Err("Unsupported key");
},
}
}
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 { match named_key {
NamedKey::PageDown => Key::PageDown, ProtobufNamedKey::PageDown => BareKey::PageDown,
NamedKey::PageUp => Key::PageUp, ProtobufNamedKey::PageUp => BareKey::PageUp,
NamedKey::LeftArrow => Key::Left, ProtobufNamedKey::LeftArrow => BareKey::Left,
NamedKey::DownArrow => Key::Down, ProtobufNamedKey::DownArrow => BareKey::Down,
NamedKey::UpArrow => Key::Up, ProtobufNamedKey::UpArrow => BareKey::Up,
NamedKey::RightArrow => Key::Right, ProtobufNamedKey::RightArrow => BareKey::Right,
NamedKey::Home => Key::Home, ProtobufNamedKey::Home => BareKey::Home,
NamedKey::End => Key::End, ProtobufNamedKey::End => BareKey::End,
NamedKey::Backspace => Key::Backspace, ProtobufNamedKey::Backspace => BareKey::Backspace,
NamedKey::Delete => Key::Delete, ProtobufNamedKey::Delete => BareKey::Delete,
NamedKey::Insert => Key::Insert, ProtobufNamedKey::Insert => BareKey::Insert,
NamedKey::F1 => Key::F(1), ProtobufNamedKey::F1 => BareKey::F(1),
NamedKey::F2 => Key::F(2), ProtobufNamedKey::F2 => BareKey::F(2),
NamedKey::F3 => Key::F(3), ProtobufNamedKey::F3 => BareKey::F(3),
NamedKey::F4 => Key::F(4), ProtobufNamedKey::F4 => BareKey::F(4),
NamedKey::F5 => Key::F(5), ProtobufNamedKey::F5 => BareKey::F(5),
NamedKey::F6 => Key::F(6), ProtobufNamedKey::F6 => BareKey::F(6),
NamedKey::F7 => Key::F(7), ProtobufNamedKey::F7 => BareKey::F(7),
NamedKey::F8 => Key::F(8), ProtobufNamedKey::F8 => BareKey::F(8),
NamedKey::F9 => Key::F(9), ProtobufNamedKey::F9 => BareKey::F(9),
NamedKey::F10 => Key::F(10), ProtobufNamedKey::F10 => BareKey::F(10),
NamedKey::F11 => Key::F(11), ProtobufNamedKey::F11 => BareKey::F(11),
NamedKey::F12 => Key::F(12), ProtobufNamedKey::F12 => BareKey::F(12),
NamedKey::Tab => Key::BackTab, ProtobufNamedKey::Tab => BareKey::Tab,
NamedKey::Esc => Key::Esc, ProtobufNamedKey::Esc => BareKey::Esc,
ProtobufNamedKey::CapsLock => BareKey::CapsLock,
ProtobufNamedKey::ScrollLock => BareKey::ScrollLock,
ProtobufNamedKey::PrintScreen => BareKey::PrintScreen,
ProtobufNamedKey::Pause => BareKey::Pause,
ProtobufNamedKey::Menu => BareKey::Menu,
ProtobufNamedKey::NumLock => BareKey::NumLock,
ProtobufNamedKey::Enter => BareKey::Enter,
} }
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 713 assertion_line: 740
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -32,4 +32,5 @@ Options {
styled_underlines: None, styled_underlines: None,
serialization_interval: None, serialization_interval: None,
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None,
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 741 assertion_line: 768
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -32,4 +32,5 @@ Options {
styled_underlines: None, styled_underlines: None,
serialization_interval: None, serialization_interval: None,
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None,
} }

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 700 assertion_line: 727
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -30,4 +30,5 @@ Options {
styled_underlines: None, styled_underlines: None,
serialization_interval: None, serialization_interval: None,
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None,
} }

View file

@ -1,60 +1,89 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 798 assertion_line: 825
expression: "format!(\"{:#?}\", config)" expression: "format!(\"{:#?}\", config)"
--- ---
Config { Config {
keybinds: { keybinds: {
Normal: { Normal: {
Char( KeyWithModifier {
'b', bare_key: Char(
): [ 'b',
),
key_modifiers: {},
}: [
SwitchToMode( SwitchToMode(
Session, Session,
), ),
], ],
Ctrl( KeyWithModifier {
'b', bare_key: Char(
): [ 'b',
),
key_modifiers: {
Ctrl,
},
}: [
SwitchToMode( SwitchToMode(
Resize, Resize,
), ),
], ],
Ctrl( KeyWithModifier {
'c', bare_key: Char(
): [ 'c',
),
key_modifiers: {
Ctrl,
},
}: [
SwitchToMode( SwitchToMode(
Resize, Resize,
), ),
], ],
}, },
Resize: { Resize: {
Char( KeyWithModifier {
'b', bare_key: Char(
): [ 'b',
),
key_modifiers: {},
}: [
SwitchToMode( SwitchToMode(
Locked, Locked,
), ),
], ],
Ctrl( KeyWithModifier {
'c', bare_key: Char(
): [ 'c',
),
key_modifiers: {
Ctrl,
},
}: [
SwitchToMode( SwitchToMode(
Resize, Resize,
), ),
], ],
}, },
Scroll: { Scroll: {
Char( KeyWithModifier {
'b', bare_key: Char(
): [ 'b',
),
key_modifiers: {},
}: [
SwitchToMode( SwitchToMode(
Locked, Locked,
), ),
], ],
Ctrl( KeyWithModifier {
'c', bare_key: Char(
): [ 'c',
),
key_modifiers: {
Ctrl,
},
}: [
SwitchToMode( SwitchToMode(
Resize, Resize,
), ),
@ -88,6 +117,7 @@ Config {
styled_underlines: None, styled_underlines: None,
serialization_interval: None, serialization_interval: None,
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None,
}, },
themes: {}, themes: {},
plugins: PluginAliases { plugins: PluginAliases {

View file

@ -1,6 +1,6 @@
--- ---
source: zellij-utils/src/setup.rs source: zellij-utils/src/setup.rs
assertion_line: 723 assertion_line: 750
expression: "format!(\"{:#?}\", options)" expression: "format!(\"{:#?}\", options)"
--- ---
Options { Options {
@ -32,4 +32,5 @@ Options {
styled_underlines: None, styled_underlines: None,
serialization_interval: None, serialization_interval: None,
disable_session_metadata: None, disable_session_metadata: None,
support_kitty_keyboard_protocol: None,
} }