From 62c37a87cca5aaa8b0e580b973b93513ed616dba Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Mon, 27 May 2024 16:15:09 +0200 Subject: [PATCH] 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 --- Cargo.lock | 1 + .../fixture-plugin-for-tests/src/main.rs | 142 +- default-plugins/session-manager/src/main.rs | 407 +- .../session-manager/src/new_session_info.rs | 17 +- default-plugins/status-bar/src/first_line.rs | 387 +- default-plugins/status-bar/src/main.rs | 323 +- default-plugins/status-bar/src/second_line.rs | 158 +- .../status-bar/src/tip/data/quicknav.rs | 6 +- default-plugins/strider/src/main.rs | 27 +- src/tests/e2e/cases.rs | 2 + src/tests/e2e/remote_runner.rs | 2 + ...2e__cases__quit_and_resurrect_session.snap | 4 +- ...t_session_with_viewport_serialization.snap | 4 +- ...__status_bar_loads_custom_keybindings.snap | 4 +- ...ts__e2e__cases__toggle_floating_panes.snap | 4 +- zellij-client/src/input_handler.rs | 32 +- zellij-client/src/keyboard_parser.rs | 1779 +++++ zellij-client/src/lib.rs | 31 +- zellij-client/src/stdin_handler.rs | 20 + zellij-server/src/panes/grid.rs | 41 + zellij-server/src/panes/plugin_pane.rs | 71 +- zellij-server/src/panes/terminal_pane.rs | 269 +- zellij-server/src/panes/unit/grid_tests.rs | 226 + .../src/panes/unit/search_in_pane_tests.rs | 2 + .../src/panes/unit/terminal_pane_tests.rs | 24 + .../src/plugins/unit/plugin_tests.rs | 156 +- ...gin_tests__write_chars_plugin_command.snap | 4 +- ...s__plugin_tests__write_plugin_command.snap | 4 +- zellij-server/src/plugins/wasm_bridge.rs | 2 - zellij-server/src/plugins/zellij_exports.rs | 2 +- zellij-server/src/route.rs | 13 +- zellij-server/src/screen.rs | 28 +- zellij-server/src/tab/layout_applier.rs | 5 + zellij-server/src/tab/mod.rs | 170 +- .../src/tab/unit/tab_integration_tests.rs | 24 +- zellij-server/src/tab/unit/tab_tests.rs | 16 +- zellij-server/src/unit/screen_tests.rs | 4 + zellij-utils/Cargo.toml | 1 + zellij-utils/assets/config/default.kdl | 5 + zellij-utils/assets/plugins/compact-bar.wasm | Bin 906100 -> 906653 bytes .../plugins/fixture-plugin-for-tests.wasm | Bin 894202 -> 893835 bytes .../assets/plugins/session-manager.wasm | Bin 975318 -> 976065 bytes zellij-utils/assets/plugins/status-bar.wasm | Bin 1060199 -> 1060689 bytes zellij-utils/assets/plugins/strider.wasm | Bin 983290 -> 983300 bytes zellij-utils/assets/plugins/tab-bar.wasm | Bin 890268 -> 890283 bytes zellij-utils/assets/prost/api.key.rs | 29 + zellij-utils/src/data.rs | 637 +- zellij-utils/src/input/actions.rs | 6 +- zellij-utils/src/input/keybinds.rs | 43 +- zellij-utils/src/input/mod.rs | 99 +- zellij-utils/src/input/options.rs | 15 + zellij-utils/src/input/unit/keybinds_test.rs | 124 +- zellij-utils/src/kdl/mod.rs | 28 +- zellij-utils/src/plugin_api/action.rs | 4 +- zellij-utils/src/plugin_api/event.rs | 66 +- zellij-utils/src/plugin_api/key.proto | 10 + zellij-utils/src/plugin_api/key.rs | 384 +- ...cli_arguments_override_config_options.snap | 3 +- ...cli_arguments_override_layout_options.snap | 3 +- ...efault_config_with_no_cli_arguments-3.snap | 3 +- ..._default_config_with_no_cli_arguments.snap | 6814 ++++++++++------- ...out_env_vars_override_config_env_vars.snap | 6814 ++++++++++------- ...out_keybinds_override_config_keybinds.snap | 74 +- ...ayout_options_override_config_options.snap | 3 +- ..._layout_themes_override_config_themes.snap | 6814 ++++++++++------- ..._ui_config_overrides_config_ui_config.snap | 6814 ++++++++++------- 66 files changed, 21302 insertions(+), 11902 deletions(-) create mode 100644 zellij-client/src/keyboard_parser.rs diff --git a/Cargo.lock b/Cargo.lock index 1988641c..f3292317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5306,6 +5306,7 @@ dependencies = [ "async-channel", "async-std", "backtrace", + "bitflags 2.5.0", "clap", "clap_complete", "colored", diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index e9555dac..f504eec8 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -72,11 +72,11 @@ impl ZellijPlugin for State { fn update(&mut self, event: Event) -> bool { match &event { - Event::Key(key) => match key { - Key::Char('a') => { + Event::Key(key) => match key.bare_key { + BareKey::Char('a') if key.has_no_modifiers() => { switch_to_input_mode(&InputMode::Tab); }, - Key::Char('b') => { + BareKey::Char('b') if key.has_no_modifiers() => { new_tabs_with_layout( "layout { tab { @@ -90,85 +90,87 @@ impl ZellijPlugin for State { }", ); }, - Key::Char('c') => new_tab(), - Key::Char('d') => go_to_next_tab(), - Key::Char('e') => go_to_previous_tab(), - Key::Char('f') => { + BareKey::Char('c') if key.has_no_modifiers() => new_tab(), + BareKey::Char('d') if key.has_no_modifiers() => go_to_next_tab(), + BareKey::Char('e') if key.has_no_modifiers() => go_to_previous_tab(), + BareKey::Char('f') if key.has_no_modifiers() => { let resize = Resize::Increase; resize_focused_pane(resize) }, - Key::Char('g') => { + BareKey::Char('g') if key.has_no_modifiers() => { let resize = Resize::Increase; let direction = Direction::Left; resize_focused_pane_with_direction(resize, direction); }, - Key::Char('h') => focus_next_pane(), - Key::Char('i') => focus_previous_pane(), - Key::Char('j') => { + BareKey::Char('h') if key.has_no_modifiers() => focus_next_pane(), + BareKey::Char('i') if key.has_no_modifiers() => focus_previous_pane(), + BareKey::Char('j') if key.has_no_modifiers() => { let direction = Direction::Left; move_focus(direction) }, - Key::Char('k') => { + BareKey::Char('k') if key.has_no_modifiers() => { let direction = Direction::Left; move_focus_or_tab(direction) }, - Key::Char('l') => detach(), - Key::Char('m') => edit_scrollback(), - Key::Char('n') => { + BareKey::Char('l') if key.has_no_modifiers() => detach(), + BareKey::Char('m') if key.has_no_modifiers() => edit_scrollback(), + BareKey::Char('n') if key.has_no_modifiers() => { let bytes = vec![102, 111, 111]; write(bytes) }, - Key::Char('o') => { + BareKey::Char('o') if key.has_no_modifiers() => { let chars = "foo"; write_chars(chars); }, - Key::Char('p') => toggle_tab(), - Key::Char('q') => move_pane(), - Key::Char('r') => { + BareKey::Char('p') if key.has_no_modifiers() => toggle_tab(), + BareKey::Char('q') if key.has_no_modifiers() => move_pane(), + BareKey::Char('r') if key.has_no_modifiers() => { let direction = Direction::Left; move_pane_with_direction(direction) }, - Key::Char('s') => clear_screen(), - Key::Char('t') => scroll_up(), - Key::Char('u') => scroll_down(), - Key::Char('v') => scroll_to_top(), - Key::Char('w') => scroll_to_bottom(), - Key::Char('x') => page_scroll_up(), - Key::Char('y') => page_scroll_down(), - Key::Char('z') => toggle_focus_fullscreen(), - Key::Char('1') => toggle_pane_frames(), - Key::Char('2') => toggle_pane_embed_or_eject(), - Key::Char('3') => undo_rename_pane(), - Key::Char('4') => close_focus(), - Key::Char('5') => toggle_active_tab_sync(), - Key::Char('6') => close_focused_tab(), - Key::Char('7') => undo_rename_tab(), - Key::Char('8') => quit_zellij(), - Key::Ctrl('a') => previous_swap_layout(), - Key::Ctrl('b') => next_swap_layout(), - Key::Ctrl('c') => { + BareKey::Char('s') if key.has_no_modifiers() => clear_screen(), + BareKey::Char('t') if key.has_no_modifiers() => scroll_up(), + BareKey::Char('u') if key.has_no_modifiers() => scroll_down(), + BareKey::Char('v') if key.has_no_modifiers() => scroll_to_top(), + BareKey::Char('w') if key.has_no_modifiers() => scroll_to_bottom(), + BareKey::Char('x') if key.has_no_modifiers() => page_scroll_up(), + BareKey::Char('y') if key.has_no_modifiers() => page_scroll_down(), + BareKey::Char('z') if key.has_no_modifiers() => toggle_focus_fullscreen(), + BareKey::Char('1') if key.has_no_modifiers() => toggle_pane_frames(), + BareKey::Char('2') if key.has_no_modifiers() => toggle_pane_embed_or_eject(), + BareKey::Char('3') if key.has_no_modifiers() => undo_rename_pane(), + BareKey::Char('4') if key.has_no_modifiers() => close_focus(), + BareKey::Char('5') if key.has_no_modifiers() => toggle_active_tab_sync(), + BareKey::Char('6') if key.has_no_modifiers() => close_focused_tab(), + BareKey::Char('7') if key.has_no_modifiers() => undo_rename_tab(), + BareKey::Char('8') if key.has_no_modifiers() => quit_zellij(), + BareKey::Char('a') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + previous_swap_layout() + }, + 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"; go_to_tab_name(tab_name) }, - Key::Ctrl('d') => { + BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let tab_name = "my tab name"; focus_or_create_tab(tab_name) }, - Key::Ctrl('e') => { + BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let tab_index = 2; 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"; start_or_reload_plugin(plugin_url) }, - Key::Ctrl('g') => { + BareKey::Char('g') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file(FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), ..Default::default() }); }, - Key::Ctrl('h') => { + BareKey::Char('h') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file_floating( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), @@ -177,14 +179,14 @@ impl ZellijPlugin for State { None, ); }, - Key::Ctrl('i') => { + BareKey::Char('i') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file(FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), line_number: Some(42), ..Default::default() }); }, - Key::Ctrl('j') => { + BareKey::Char('j') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_file_floating( FileToOpen { path: std::path::PathBuf::from("/path/to/my/file.rs"), @@ -194,23 +196,23 @@ impl ZellijPlugin for State { 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()); }, - Key::Ctrl('l') => { + BareKey::Char('l') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_terminal_floating( std::path::PathBuf::from("/path/to/my/file.rs").as_path(), None, ); }, - Key::Ctrl('m') => { + BareKey::Char('m') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_command_pane(CommandToRun { path: std::path::PathBuf::from("/path/to/my/file.rs"), args: vec!["arg1".to_owned(), "arg2".to_owned()], ..Default::default() }); }, - Key::Ctrl('n') => { + BareKey::Char('n') if key.has_modifiers(&[KeyModifier::Ctrl]) => { open_command_pane_floating( CommandToRun { path: std::path::PathBuf::from("/path/to/my/file.rs"), @@ -220,51 +222,51 @@ impl ZellijPlugin for State { None, ); }, - Key::Ctrl('o') => { + BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_tab_to(1); }, - Key::Ctrl('p') => { + BareKey::Char('p') if key.has_modifiers(&[KeyModifier::Ctrl]) => { hide_self(); }, - Key::Ctrl('q') => { + BareKey::Char('q') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let should_float_if_hidden = false; show_self(should_float_if_hidden); }, - Key::Ctrl('r') => { + BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Ctrl]) => { close_terminal_pane(1); }, - Key::Ctrl('s') => { + BareKey::Char('s') if key.has_modifiers(&[KeyModifier::Ctrl]) => { close_plugin_pane(1); }, - Key::Ctrl('t') => { + BareKey::Char('t') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let should_float_if_hidden = false; 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; 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"); }, - Key::Ctrl('w') => { + BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => { 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"); }, - Key::Ctrl('z') => { + BareKey::Char('z') if key.has_modifiers(&[KeyModifier::Ctrl]) => { go_to_tab_name(&format!("{:?}", self.configuration)); }, - Key::Ctrl('1') => { + BareKey::Char('1') if key.has_modifiers(&[KeyModifier::Ctrl]) => { request_permission(&[PermissionType::ReadApplicationState]); }, - Key::Ctrl('2') => { + BareKey::Char('2') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut context = BTreeMap::new(); context.insert("user_key_1".to_owned(), "user_value_1".to_owned()); run_command(&["ls", "-l"], context); }, - Key::Ctrl('3') => { + BareKey::Char('3') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut context = BTreeMap::new(); context.insert("user_key_2".to_owned(), "user_value_2".to_owned()); let mut env_vars = BTreeMap::new(); @@ -276,7 +278,7 @@ impl ZellijPlugin for State { context, ); }, - Key::Ctrl('4') => { + BareKey::Char('4') if key.has_modifiers(&[KeyModifier::Ctrl]) => { let mut headers = BTreeMap::new(); let mut context = BTreeMap::new(); let body = vec![1, 2, 3]; @@ -292,22 +294,24 @@ impl ZellijPlugin for State { context, ); }, - Key::Ctrl('5') => { + BareKey::Char('5') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session(Some("my_new_session")); }, - Key::Ctrl('6') => disconnect_other_clients(), - Key::Ctrl('7') => { + BareKey::Char('6') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + disconnect_other_clients() + }, + BareKey::Char('7') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session_with_layout( Some("my_other_new_session"), LayoutInfo::BuiltIn("compact".to_owned()), 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(); file.write_all(b"Hi there!").unwrap(); }, - Key::Ctrl('9') => { + BareKey::Char('9') if key.has_modifiers(&[KeyModifier::Ctrl]) => { switch_session_with_layout( Some("my_other_new_session_with_cwd"), LayoutInfo::BuiltIn("compact".to_owned()), diff --git a/default-plugins/session-manager/src/main.rs b/default-plugins/session-manager/src/main.rs index 9f37a34a..05a33286 100644 --- a/default-plugins/session-manager/src/main.rs +++ b/default-plugins/session-manager/src/main.rs @@ -175,7 +175,7 @@ impl State { fn reset_selected_index(&mut self) { 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() { self.error = None; return true; @@ -186,197 +186,246 @@ impl State { 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; - if let Key::Down = key { - self.new_session_info.handle_key(key); - 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 { + match key.bare_key { + BareKey::Down if key.has_no_modifiers() => { self.new_session_info.handle_key(key); - } - should_render = true; - } else if let Key::Backspace = key { - self.new_session_info.handle_key(key); - should_render = true; - } else if let Key::Ctrl('w') = key { - self.active_screen = ActiveScreen::NewSession; - should_render = true; - } else if let Key::BackTab = key { - self.toggle_active_screen(); - should_render = true; - } else if let Key::Ctrl('f') = key { - let request_id = Uuid::new_v4(); - let mut config = BTreeMap::new(); - let mut args = BTreeMap::new(); - self.request_ids.push(request_id.to_string()); - // we insert this into the config so that a new plugin will be opened (the plugin's - // uniqueness is determined by its name/url as well as its config) - config.insert("request_id".to_owned(), request_id.to_string()); - // we also insert this into the args so that the plugin will have an easier access to - // it - args.insert("request_id".to_owned(), request_id.to_string()); - 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; - } else if let Key::Ctrl('c') = key { - self.new_session_info.new_session_folder = None; - should_render = true; - } else if let Key::Esc = key { - self.new_session_info.handle_key(key); - should_render = true; + should_render = true; + }, + BareKey::Up if key.has_no_modifiers() => { + self.new_session_info.handle_key(key); + should_render = true; + }, + BareKey::Char(character) if key.has_no_modifiers() => { + if character == '\n' { + self.handle_selection(); + } else { + self.new_session_info.handle_key(key); + } + should_render = true; + }, + BareKey::Backspace if key.has_no_modifiers() => { + self.new_session_info.handle_key(key); + should_render = true; + }, + BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + self.active_screen = ActiveScreen::NewSession; + should_render = true; + }, + BareKey::Tab if key.has_no_modifiers() => { + self.toggle_active_screen(); + should_render = true; + }, + BareKey::Char('f') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + let request_id = Uuid::new_v4(); + let mut config = BTreeMap::new(); + let mut args = BTreeMap::new(); + self.request_ids.push(request_id.to_string()); + // we insert this into the config so that a new plugin will be opened (the plugin's + // uniqueness is determined by its name/url as well as its config) + config.insert("request_id".to_owned(), request_id.to_string()); + // we also insert this into the args so that the plugin will have an easier access to + // it + args.insert("request_id".to_owned(), request_id.to_string()); + 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 } - 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; if self.show_kill_all_sessions_warning { - if let Key::Char('y') = key { - let all_other_sessions = self.sessions.all_other_sessions(); - kill_sessions(&all_other_sessions); - self.reset_selected_index(); - self.search_term.clear(); - self.sessions - .update_search_term(&self.search_term, &self.colors); - self.show_kill_all_sessions_warning = false - } else if let Key::Char('n') | Key::Esc | Key::Ctrl('c') = key { - self.show_kill_all_sessions_warning = false + match key.bare_key { + BareKey::Char('y') if key.has_no_modifiers() => { + let all_other_sessions = self.sessions.all_other_sessions(); + kill_sessions(&all_other_sessions); + self.reset_selected_index(); + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + 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 if let Key::Right = key { - self.sessions.result_expand(); - should_render = true; - } else if let Key::Left = key { - self.sessions.result_shrink(); - should_render = true; - } else if let Key::Down = key { - self.sessions.move_selection_down(); - should_render = true; - } else if let Key::Up = key { - self.sessions.move_selection_up(); - should_render = true; - } else if let Key::Char(character) = key { - if character == '\n' { - self.handle_selection(); - } else if let Some(new_session_name) = self.renaming_session_name.as_mut() { - new_session_name.push(character); - } else { - self.search_term.push(character); - self.sessions - .update_search_term(&self.search_term, &self.colors); - } - should_render = true; - } else if let Key::Backspace = key { - if let Some(new_session_name) = self.renaming_session_name.as_mut() { - if new_session_name.is_empty() { - self.renaming_session_name = None; - } else { - new_session_name.pop(); - } - } else { - self.search_term.pop(); - self.sessions - .update_search_term(&self.search_term, &self.colors); - } - should_render = true; - } else if let Key::Ctrl('w') = key { - self.active_screen = ActiveScreen::NewSession; - should_render = true; - } else if let Key::Ctrl('r') = key { - self.renaming_session_name = Some(String::new()); - should_render = true; - } else if let Key::Delete = key { - if let Some(selected_session_name) = self.sessions.get_selected_session_name() { - kill_sessions(&[selected_session_name]); - self.reset_selected_index(); - self.search_term.clear(); - self.sessions - .update_search_term(&self.search_term, &self.colors); - } else { - self.show_error("Must select session before killing it."); - } - should_render = true; - } else if let Key::Ctrl('d') = key { - let all_other_sessions = self.sessions.all_other_sessions(); - if all_other_sessions.is_empty() { - self.show_error("No other sessions to kill. Quit to kill the current one."); - } else { - self.show_kill_all_sessions_warning = true; - } - should_render = true; - } else if let Key::Ctrl('x') = key { - disconnect_other_clients(); - } else if let Key::Ctrl('c') = key { - if !self.search_term.is_empty() { - self.search_term.clear(); - self.sessions - .update_search_term(&self.search_term, &self.colors); - self.reset_selected_index(); - } else if !self.is_welcome_screen { - self.reset_selected_index(); - hide_self(); - } - should_render = true; - } else if let Key::BackTab = key { - self.toggle_active_screen(); - should_render = true; - } else if let Key::Esc = key { - if self.renaming_session_name.is_some() { - self.renaming_session_name = None; - should_render = true; - } else if !self.is_welcome_screen { - hide_self(); + } else { + match key.bare_key { + BareKey::Right if key.has_no_modifiers() => { + self.sessions.result_expand(); + should_render = true; + }, + BareKey::Left if key.has_no_modifiers() => { + self.sessions.result_shrink(); + should_render = true; + }, + BareKey::Down if key.has_no_modifiers() => { + self.sessions.move_selection_down(); + should_render = true; + }, + BareKey::Up if key.has_no_modifiers() => { + self.sessions.move_selection_up(); + should_render = true; + }, + BareKey::Char(character) if key.has_no_modifiers() => { + if character == '\n' { + self.handle_selection(); + } else if let Some(new_session_name) = self.renaming_session_name.as_mut() { + new_session_name.push(character); + } else { + self.search_term.push(character); + self.sessions + .update_search_term(&self.search_term, &self.colors); + } + should_render = true; + }, + BareKey::Backspace if key.has_no_modifiers() => { + if let Some(new_session_name) = self.renaming_session_name.as_mut() { + if new_session_name.is_empty() { + self.renaming_session_name = None; + } else { + new_session_name.pop(); + } + } else { + self.search_term.pop(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + } + should_render = true; + }, + BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + self.active_screen = ActiveScreen::NewSession; + should_render = true; + }, + BareKey::Char('r') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + self.renaming_session_name = Some(String::new()); + should_render = true; + }, + BareKey::Delete if key.has_no_modifiers() => { + if let Some(selected_session_name) = self.sessions.get_selected_session_name() { + kill_sessions(&[selected_session_name]); + self.reset_selected_index(); + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + } else { + self.show_error("Must select session before killing it."); + } + should_render = true; + }, + BareKey::Char('d') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + let all_other_sessions = self.sessions.all_other_sessions(); + if all_other_sessions.is_empty() { + self.show_error("No other sessions to kill. Quit to kill the current one."); + } else { + self.show_kill_all_sessions_warning = true; + } + should_render = true; + }, + BareKey::Char('x') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + disconnect_other_clients() + }, + BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + if !self.search_term.is_empty() { + self.search_term.clear(); + self.sessions + .update_search_term(&self.search_term, &self.colors); + self.reset_selected_index(); + } else if !self.is_welcome_screen { + 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 } - 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; - if let Key::Down = key { - self.resurrectable_sessions.move_selection_down(); - should_render = true; - } else if let Key::Up = key { - self.resurrectable_sessions.move_selection_up(); - should_render = true; - } else if let Key::Char(character) = key { - if character == '\n' { - self.handle_selection(); - } else { - self.resurrectable_sessions.handle_character(character); - } - should_render = true; - } else if let Key::Backspace = key { - self.resurrectable_sessions.handle_backspace(); - should_render = true; - } else if let Key::Ctrl('w') = key { - self.active_screen = ActiveScreen::NewSession; - should_render = true; - } else if let Key::BackTab = key { - self.toggle_active_screen(); - should_render = true; - } else if let Key::Delete = key { - self.resurrectable_sessions.delete_selected_session(); - should_render = true; - } else if let Key::Ctrl('d') = key { - self.resurrectable_sessions - .show_delete_all_sessions_warning(); - should_render = true; - } else if let Key::Esc = key { - if !self.is_welcome_screen { - hide_self(); - } + match key.bare_key { + BareKey::Down if key.has_no_modifiers() => { + self.resurrectable_sessions.move_selection_down(); + should_render = true; + }, + BareKey::Up if key.has_no_modifiers() => { + self.resurrectable_sessions.move_selection_up(); + should_render = true; + }, + BareKey::Char(character) if key.has_no_modifiers() => { + if character == '\n' { + self.handle_selection(); + } else { + self.resurrectable_sessions.handle_character(character); + } + should_render = true; + }, + BareKey::Backspace if key.has_no_modifiers() => { + self.resurrectable_sessions.handle_backspace(); + should_render = true; + }, + BareKey::Char('w') if key.has_modifiers(&[KeyModifier::Ctrl]) => { + self.active_screen = ActiveScreen::NewSession; + should_render = true; + }, + BareKey::Tab if key.has_no_modifiers() => { + self.toggle_active_screen(); + should_render = true; + }, + BareKey::Delete if key.has_no_modifiers() => { + self.resurrectable_sessions.delete_selected_session(); + 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 } diff --git a/default-plugins/session-manager/src/new_session_info.rs b/default-plugins/session-manager/src/new_session_info.rs index 3527b7dc..a6ff7901 100644 --- a/default-plugins/session-manager/src/new_session_info.rs +++ b/default-plugins/session-manager/src/new_session_info.rs @@ -70,21 +70,24 @@ impl NewSessionInfo { }, } } - pub fn handle_key(&mut self, key: Key) { - match key { - Key::Backspace => { + pub fn handle_key(&mut self, key: KeyWithModifier) { + match key.bare_key { + BareKey::Backspace if key.has_no_modifiers() => { self.handle_backspace(); }, - Key::Ctrl('c') | Key::Esc => { + BareKey::Char('c') if key.has_modifiers(&[KeyModifier::Ctrl]) => { self.handle_break(); }, - 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); }, - Key::Up => { + BareKey::Up if key.has_no_modifiers() => { self.move_selection_up(); }, - Key::Down => { + BareKey::Down if key.has_no_modifiers() => { self.move_selection_down(); }, _ => {}, diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 4ac09064..1fffd755 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -4,14 +4,14 @@ use zellij_tile::prelude::*; use crate::color_elements; 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}; struct KeyShortcut { mode: KeyMode, action: KeyAction, - key: Option, + key: Option, } #[derive(PartialEq)] @@ -35,7 +35,7 @@ enum KeyMode { } impl KeyShortcut { - pub fn new(mode: KeyMode, action: KeyAction, key: Option) -> Self { + pub fn new(mode: KeyMode, action: KeyAction, key: Option) -> Self { KeyShortcut { mode, action, key } } @@ -52,25 +52,36 @@ impl KeyShortcut { KeyAction::Tmux => String::from("TMUX"), } } - pub fn letter_shortcut(&self, with_prefix: bool) -> String { - let key = match self.key { - Some(k) => k, + pub fn with_shortened_modifiers(&self, common_modifiers: &Vec) -> String { + let key = match &self.key { + Some(k) => k.strip_common_modifiers(common_modifiers), 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::>() + .join("-"); + if shortened_modifiers.is_empty() { format!("{}", key) } else { - match key { - Key::F(c) => format!("{}", c), - Key::CtrlF(n) => format!("F{}", n), - Key::AltF(n) => format!("F{}", n), - Key::Ctrl(c) => format!("{}", c), - Key::Char(_) => format!("{}", key), - Key::Alt(c) => format!("{}", c), - _ => String::from("??"), - } + format!("{} {}", shortened_modifiers, key.bare_key) } } + pub fn letter_shortcut(&self, common_modifiers: &Vec) -> 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. @@ -96,14 +107,15 @@ fn long_mode_shortcut( key: &KeyShortcut, palette: ColoredElements, separator: &str, - shared_super: bool, + common_modifiers: &Vec, 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.letter_shortcut(!shared_super), + (_, Some(_)) => key.letter_shortcut(common_modifiers), }; let colors = match key.mode { @@ -112,7 +124,59 @@ fn long_mode_shortcut( KeyMode::Selected => palette.selected, 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, + 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 { separator @@ -165,13 +229,14 @@ fn short_mode_shortcut( key: &KeyShortcut, palette: ColoredElements, separator: &str, - shared_super: bool, + common_modifiers: &Vec, first_tile: bool, ) -> LinePart { + 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.letter_shortcut(!shared_super), + (_, Some(_)) => key.letter_shortcut(common_modifiers), }; let colors = match key.mode { @@ -180,7 +245,7 @@ fn short_mode_shortcut( KeyMode::Selected => palette.selected, KeyMode::Disabled => palette.disabled, }; - let start_separator = if !shared_super && first_tile { + let start_separator = if !has_common_modifiers && first_tile { "" } else { separator @@ -206,11 +271,23 @@ fn key_indicators( mode_info: &ModeInfo, ) -> LinePart { // Print full-width hints - let mut line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 0; - for ctrl_key in keys { + let (shared_modifiers, mut line_part) = superkey(palette, separator, mode_info); + for key in keys { 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.len += key.len; } @@ -219,11 +296,10 @@ fn key_indicators( } // Full-width doesn't fit, try shortened hints (just keybindings, no meanings/actions) - line_part = superkey(palette, separator, mode_info); - let shared_super = line_part.len > 0; - for ctrl_key in keys { + line_part = superkey(palette, separator, mode_info).1; + for key in keys { 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.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 /// 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. -pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { +pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { mode_info .get_mode_keybinds() .iter() @@ -355,7 +431,19 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { Some(vac) => { // We ignore certain "default" keybindings that switch back to normal InputMode. // 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; } if let actions::Action::SwitchToMode(mode) = vac { @@ -368,12 +456,12 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { | InputMode::Resize | InputMode::Move | InputMode::Scroll - | InputMode::Session => Some(*key), + | InputMode::Session => Some(key.clone()), _ => None, }; } if let actions::Action::Quit = vac { - return Some(*key); + return Some(key.clone()); } // Not a `SwitchToMode` or `Quit` action, ignore None @@ -382,36 +470,69 @@ pub fn mode_switch_keys(mode_info: &ModeInfo) -> Vec { .collect() } -pub fn superkey(palette: ColoredElements, separator: &str, mode_info: &ModeInfo) -> LinePart { +pub fn superkey( + palette: ColoredElements, + separator: &str, + mode_info: &ModeInfo, +) -> (Vec, LinePart) { // Find a common modifier if any - let prefix_text = match get_common_modifier(mode_switch_keys(mode_info).iter().collect()) { - Some(text) => { - if mode_info.capabilities.arrow_fonts { - // Add extra space in simplified ui - format!(" {} + ", text) - } else { - format!(" {} +", text) - } - }, - _ => return LinePart::default(), + let common_modifiers = get_common_modifiers(mode_switch_keys(mode_info).iter().collect()); + if common_modifiers.is_empty() { + return (common_modifiers, LinePart::default()); + } + + let prefix_text = if mode_info.capabilities.arrow_fonts { + // Add extra space in simplified ui + format!( + " {} + ", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ) + } else { + format!( + " {} +", + common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-") + ) }; let prefix = palette.superkey_prefix.paint(&prefix_text); let suffix_separator = palette.superkey_suffix_separator.paint(separator); - LinePart { - part: ANSIStrings(&[prefix, suffix_separator]).to_string(), - len: prefix_text.chars().count() + separator.chars().count(), - } + ( + common_modifiers, + LinePart { + part: ANSIStrings(&[prefix, suffix_separator]).to_string(), + len: prefix_text.chars().count() + separator.chars().count(), + }, + ) } -pub fn to_char(kv: Vec) -> Option { +pub fn to_char(kv: Vec) -> Option { let key = kv .iter() .filter(|key| { // 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::>() + .collect::>() .into_iter() .next(); // Maybe the user bound one of the ignored keys? @@ -593,10 +714,14 @@ mod tests { #[test] 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 ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = long_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -608,11 +733,11 @@ mod tests { let key = KeyShortcut::new( KeyMode::Unselected, KeyAction::Session, - Some(Key::Char('0')), + Some(KeyWithModifier::new(BareKey::Char('0'))), ); 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); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -624,11 +749,11 @@ mod tests { let key = KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Session, - Some(Key::Char('0')), + Some(KeyWithModifier::new(BareKey::Char('0'))), ); 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); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -640,7 +765,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); 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); assert_eq!(ret, "".to_string()); @@ -649,10 +774,14 @@ mod tests { #[test] // First tile doesn't print a starting separator 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 ret = long_mode_shortcut(&key, color, "+", false, true); + let ret = long_mode_shortcut(&key, color, "+", &vec![], true); let ret = unstyle(ret); assert_eq!(ret, " <0> SESSION +".to_string()); @@ -661,10 +790,14 @@ mod tests { #[test] // Modifier is the superkey, mustn't appear in angled brackets 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 ret = long_mode_shortcut(&key, color, "+", true, false); + let ret = long_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], false); let ret = unstyle(ret); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -673,22 +806,30 @@ mod tests { #[test] // Modifier must be in the angled brackets 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 ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = long_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); - assert_eq!(ret, "+ SESSION +".to_string()); + assert_eq!(ret, "+ SESSION +".to_string()); } #[test] // 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() { - 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 ret = long_mode_shortcut(&key, color, "+", false, false); + let ret = long_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -700,7 +841,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::Disabled, KeyAction::Session, None); 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); 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 // ignore **first** here. 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 ret = long_mode_shortcut(&key, color, "+", true, true); + let ret = long_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], true); let ret = unstyle(ret); assert_eq!(ret, "+ <0> SESSION +".to_string()); @@ -722,10 +867,14 @@ mod tests { #[test] 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 ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = short_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); assert_eq!(ret, "+ 0 +".to_string()); @@ -733,21 +882,29 @@ mod tests { #[test] 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 ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = short_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); - assert_eq!(ret, "+ Ctrl+0 +".to_string()); + assert_eq!(ret, "+ Ctrl 0 +".to_string()); } #[test] 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 ret = short_mode_shortcut(&key, color, "+", true, false); + let ret = short_mode_shortcut(&key, color, "+", &vec![KeyModifier::Ctrl], false); let ret = unstyle(ret); assert_eq!(ret, "+ 0 +".to_string()); @@ -755,10 +912,14 @@ mod tests { #[test] 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 ret = short_mode_shortcut(&key, color, "+", false, true); + let ret = short_mode_shortcut(&key, color, "+", &vec![], true); let ret = unstyle(ret); assert_eq!(ret, " 0 +".to_string()); @@ -769,11 +930,11 @@ mod tests { let key = KeyShortcut::new( KeyMode::Unselected, KeyAction::Session, - Some(Key::Char('0')), + Some(KeyWithModifier::new(BareKey::Char('0'))), ); 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); assert_eq!(ret, "+ 0 +".to_string()); @@ -784,11 +945,11 @@ mod tests { let key = KeyShortcut::new( KeyMode::UnselectedAlternate, KeyAction::Session, - Some(Key::Char('0')), + Some(KeyWithModifier::new(BareKey::Char('0'))), ); 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); assert_eq!(ret, "+ 0 +".to_string()); @@ -796,10 +957,14 @@ mod tests { #[test] 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 ret = short_mode_shortcut(&key, color, "+", false, false); + let ret = short_mode_shortcut(&key, color, "+", &vec![], false); let ret = unstyle(ret); assert_eq!(ret, "+ 0 +".to_string()); @@ -810,7 +975,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); 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); assert_eq!(ret, "".to_string()); @@ -821,7 +986,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::Unselected, KeyAction::Session, None); 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); assert_eq!(ret, "".to_string()); @@ -832,7 +997,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::UnselectedAlternate, KeyAction::Session, None); 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); assert_eq!(ret, "".to_string()); @@ -843,7 +1008,7 @@ mod tests { let key = KeyShortcut::new(KeyMode::Selected, KeyAction::Session, None); 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); assert_eq!(ret, "".to_string()); @@ -857,9 +1022,9 @@ mod tests { mode: InputMode::Normal, keybinds : vec![ (InputMode::Normal, vec![ - (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), - (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), - (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]), + (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]), + (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]), ]), ], ..ModeInfo::default() @@ -881,9 +1046,9 @@ mod tests { mode: InputMode::Normal, keybinds : vec![ (InputMode::Normal, vec![ - (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), - (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), - (Key::Char('c'), vec![Action::SwitchToMode(InputMode::Move)]), + (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]), + (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]), + (KeyWithModifier::new(BareKey::Char('c')), vec![Action::SwitchToMode(InputMode::Move)]), ]), ], ..ModeInfo::default() @@ -894,7 +1059,7 @@ mod tests { assert_eq!( ret, - " PANE >> RESIZE >> MOVE >".to_string() + " PANE >> RESIZE >> MOVE >".to_string() ); } @@ -905,11 +1070,11 @@ mod tests { mode: InputMode::Normal, keybinds : vec![ (InputMode::Normal, vec![ - (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), - (Key::Backspace, vec![Action::SwitchToMode(InputMode::Pane)]), - (Key::Char('\n'), vec![Action::SwitchToMode(InputMode::Tab)]), - (Key::Char('\t'), vec![Action::SwitchToMode(InputMode::Resize)]), - (Key::Left, vec![Action::SwitchToMode(InputMode::Move)]), + (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Locked)]), + (KeyWithModifier::new(BareKey::Backspace), vec![Action::SwitchToMode(InputMode::Pane)]), + (KeyWithModifier::new(BareKey::Enter), vec![Action::SwitchToMode(InputMode::Tab)]), + (KeyWithModifier::new(BareKey::Tab), vec![Action::SwitchToMode(InputMode::Resize)]), + (KeyWithModifier::new(BareKey::Left), vec![Action::SwitchToMode(InputMode::Move)]), ]), ], ..ModeInfo::default() @@ -920,7 +1085,7 @@ mod tests { assert_eq!( ret, - " LOCK >> PANE >> TAB >> RESIZE >> <←> MOVE >" + " LOCK >> PANE >> TAB >> RESIZE >> <←> MOVE >" .to_string() ); } @@ -932,11 +1097,11 @@ mod tests { mode: InputMode::Normal, keybinds : vec![ (InputMode::Normal, vec![ - (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Locked)]), - (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Pane)]), - (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Tab)]), - (Key::Ctrl('d'), vec![Action::SwitchToMode(InputMode::Resize)]), - (Key::Ctrl('e'), vec![Action::SwitchToMode(InputMode::Move)]), + (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Locked)]), + (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]), + (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Tab)]), + (KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]), + (KeyWithModifier::new(BareKey::Char('e')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]), ]), ], ..ModeInfo::default() @@ -955,9 +1120,9 @@ mod tests { mode: InputMode::Normal, keybinds : vec![ (InputMode::Normal, vec![ - (Key::Ctrl('a'), vec![Action::SwitchToMode(InputMode::Pane)]), - (Key::Ctrl('b'), vec![Action::SwitchToMode(InputMode::Resize)]), - (Key::Ctrl('c'), vec![Action::SwitchToMode(InputMode::Move)]), + (KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Pane)]), + (KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Resize)]), + (KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), vec![Action::SwitchToMode(InputMode::Move)]), ]), ], ..ModeInfo::default() diff --git a/default-plugins/status-bar/src/main.rs b/default-plugins/status-bar/src/main.rs index 1d85c7f6..6fed529f 100644 --- a/default-plugins/status-bar/src/main.rs +++ b/default-plugins/status-bar/src/main.rs @@ -324,30 +324,18 @@ impl State { } } -/// Get a common modifier key from a key vector. -/// -/// Iterates over all keys and returns any found common modifier key. Possible modifiers that will -/// be detected are "Ctrl" and "Alt". -pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { - 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; - } +pub fn get_common_modifiers(mut keyvec: Vec<&KeyWithModifier>) -> Vec { + if keyvec.is_empty() { + return vec![]; } - match modifier.is_empty() { - true => None, - false => Some(modifier.to_string()), + let mut common_modifiers = keyvec.pop().unwrap().key_modifiers.clone(); + for key in keyvec { + common_modifiers = common_modifiers + .intersection(&key.key_modifiers) + .cloned() + .collect(); } + common_modifiers.into_iter().collect() } /// Get key from action pattern(s). @@ -356,7 +344,10 @@ pub fn get_common_modifier(keyvec: Vec<&Key>) -> Option { /// 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 /// keys that trigger the action pattern are returned as vector of `Vec`. -pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec { +pub fn action_key( + keymap: &[(KeyWithModifier, Vec)], + action: &[Action], +) -> Vec { keymap .iter() .filter_map(|(key, acvec)| { @@ -367,18 +358,21 @@ pub fn action_key(keymap: &[(Key, Vec)], action: &[Action]) -> Vec .count(); if matching == acvec.len() && matching == action.len() { - Some(*key) + Some(key.clone()) } else { None } }) - .collect::>() + .collect::>() } /// Get multiple keys for multiple actions. /// /// An extension of [`action_key`] that iterates over all action tuples and collects the results. -pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> Vec { +pub fn action_key_group( + keymap: &[(KeyWithModifier, Vec)], + actions: &[&[Action]], +) -> Vec { let mut ret = vec![]; for action in actions { ret.extend(action_key(keymap, action)); @@ -406,11 +400,10 @@ pub fn action_key_group(keymap: &[(Key, Vec)], actions: &[&[Action]]) -> /// The returned Vector of [`ANSIString`] is suitable for transformation into an [`ANSIStrings`] /// type. pub fn style_key_with_modifier( - keyvec: &[Key], + keyvec: &[KeyWithModifier], palette: &Palette, background: Option, ) -> Vec> { - // Nothing to do, quit... if keyvec.is_empty() { return vec![]; } @@ -423,12 +416,18 @@ pub fn style_key_with_modifier( let orange_color = palette_match!(palette.orange); let mut ret = vec![]; - // Prints modifier key - let modifier_str = match get_common_modifier(keyvec.iter().collect()) { - Some(modifier) => modifier, - None => "".to_string(), - }; - let no_modifier = modifier_str.is_empty(); + let common_modifiers = get_common_modifiers(keyvec.iter().collect()); + + // let modifier_str = match get_common_modifier(keyvec.iter().collect()) { + // Some(modifier) => modifier, + // None => "".to_string(), + // }; + let no_common_modifier = common_modifiers.is_empty(); + let modifier_str = common_modifiers + .iter() + .map(|m| m.to_string()) + .collect::>() + .join("-"); let painted_modifier = if modifier_str.is_empty() { Style::new().paint("") } else { @@ -446,7 +445,7 @@ pub fn style_key_with_modifier( ret.push(painted_modifier); // 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 { let background = palette_match!(background); ret.push( @@ -463,15 +462,20 @@ pub fn style_key_with_modifier( let key = keyvec .iter() .map(|key| { - if no_modifier { + if no_common_modifier { format!("{}", key) } else { - match key { - Key::Ctrl(c) => format!("{}", Key::Char(*c)), - Key::CtrlF(n) => format!("{}", Key::F(*n)), - Key::Alt(c) => format!("{}", c), - Key::AltF(n) => format!("{}", Key::F(*n)), - _ => format!("{}", key), + let key_modifier_for_key = key + .key_modifiers + .iter() + .filter(|m| !common_modifiers.contains(m)) + .map(|m| m.to_string()) + .collect::>() + .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 ansi_term::unstyle; use ansi_term::ANSIStrings; - use zellij_tile::prelude::CharOrArrow; - use zellij_tile::prelude::Direction; - fn big_keymap() -> Vec<(Key, Vec)> { + fn big_keymap() -> Vec<(KeyWithModifier, Vec)> { vec![ - (Key::Char('a'), vec![Action::Quit]), - (Key::Ctrl('b'), vec![Action::ScrollUp]), - (Key::Ctrl('d'), vec![Action::ScrollDown]), + (KeyWithModifier::new(BareKey::Char('a')), vec![Action::Quit]), ( - 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)], ), ( - Key::Char('1'), + KeyWithModifier::new(BareKey::Char('1')), vec![TO_NORMAL, Action::SwitchToMode(InputMode::Locked)], ), ] @@ -559,94 +567,65 @@ pub mod tests { #[test] fn common_modifier_with_ctrl_keys() { - let keyvec = vec![Key::Ctrl('a'), Key::Ctrl('b'), Key::Ctrl('c')]; - let ret = get_common_modifier(keyvec.iter().collect()); - assert_eq!(ret, Some("Ctrl".to_string())); + 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 ret = get_common_modifiers(keyvec.iter().collect()); + assert_eq!(ret, vec![KeyModifier::Ctrl]); } #[test] fn common_modifier_with_alt_keys_chars() { let keyvec = vec![ - Key::Alt(CharOrArrow::Char('1')), - Key::Alt(CharOrArrow::Char('t')), - Key::Alt(CharOrArrow::Char('z')), + KeyWithModifier::new(BareKey::Char('1')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(), ]; - let ret = get_common_modifier(keyvec.iter().collect()); - assert_eq!(ret, Some("Alt".to_string())); - } - - #[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())); + let ret = get_common_modifiers(keyvec.iter().collect()); + assert_eq!(ret, vec![KeyModifier::Alt]); } #[test] fn common_modifier_with_mixed_alt_ctrl_keys() { let keyvec = vec![ - Key::Alt(CharOrArrow::Direction(Direction::Left)), - Key::Alt(CharOrArrow::Char('z')), - Key::Ctrl('a'), - Key::Ctrl('1'), + KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier(), + KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(), ]; - let ret = get_common_modifier(keyvec.iter().collect()); - assert_eq!(ret, None); + let ret = get_common_modifiers(keyvec.iter().collect()); + assert_eq!(ret, vec![]); // no common modifiers } #[test] fn common_modifier_with_any_keys() { - let keyvec = vec![Key::Backspace, Key::Char('f'), Key::Down]; - let ret = get_common_modifier(keyvec.iter().collect()); - assert_eq!(ret, None); - } - - #[test] - fn common_modifier_with_ctrl_and_normal_keys() { - 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); + let keyvec = vec![ + KeyWithModifier::new(BareKey::Char('1')), + KeyWithModifier::new(BareKey::Char('t')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('z')).with_alt_modifier(), + ]; + let ret = get_common_modifiers(keyvec.iter().collect()); + assert_eq!(ret, vec![]); // no common modifiers } #[test] 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]); - assert_eq!(ret, vec![Key::Char('f')]); + assert_eq!(ret, vec![KeyWithModifier::new(BareKey::Char('f'))]); } #[test] 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]); assert_eq!(ret, Vec::new()); } #[test] 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, &[]); assert_eq!(ret, Vec::new()); } @@ -655,7 +634,10 @@ pub mod tests { fn action_key_long_pattern_match_exact() { let keymap = big_keymap(); 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] @@ -669,7 +651,7 @@ pub mod tests { fn action_key_group_single_pattern() { let keymap = big_keymap(); 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] @@ -677,7 +659,13 @@ pub mod tests { let keymap = big_keymap(); let ret = action_key_group(&keymap, &[&[Action::ScrollDown], &[Action::ScrollUp]]); // 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 { @@ -686,7 +674,11 @@ pub mod tests { #[test] 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 ret = style_key_with_modifier(&keyvec, &palette, None); @@ -698,10 +690,10 @@ pub mod tests { #[test] fn style_key_with_modifier_special_group_hjkl() { let keyvec = vec![ - Key::Char('h'), - Key::Char('j'), - Key::Char('k'), - Key::Char('l'), + KeyWithModifier::new(BareKey::Char('h')), + KeyWithModifier::new(BareKey::Char('j')), + KeyWithModifier::new(BareKey::Char('k')), + KeyWithModifier::new(BareKey::Char('l')), ]; let palette = get_palette(); @@ -711,30 +703,13 @@ pub mod tests { assert_eq!(ret, "".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, "".to_string()) - } - #[test] fn style_key_with_modifier_special_group_all_arrows() { let keyvec = vec![ - Key::Char('←'), - Key::Char('↓'), - Key::Char('↑'), - Key::Char('→'), + KeyWithModifier::new(BareKey::Left), + KeyWithModifier::new(BareKey::Down), + KeyWithModifier::new(BareKey::Up), + KeyWithModifier::new(BareKey::Right), ]; let palette = get_palette(); @@ -746,7 +721,10 @@ pub mod tests { #[test] 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 ret = style_key_with_modifier(&keyvec, &palette, None); @@ -757,7 +735,10 @@ pub mod tests { #[test] 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 ret = style_key_with_modifier(&keyvec, &palette, None); @@ -769,10 +750,10 @@ pub mod tests { #[test] fn style_key_with_modifier_common_ctrl_modifier_chars() { let keyvec = vec![ - Key::Ctrl('a'), - Key::Ctrl('b'), - Key::Ctrl('c'), - Key::Ctrl('d'), + KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), + KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), + KeyWithModifier::new(BareKey::Char('c')).with_ctrl_modifier(), + KeyWithModifier::new(BareKey::Char('d')).with_ctrl_modifier(), ]; let palette = get_palette(); @@ -785,10 +766,10 @@ pub mod tests { #[test] fn style_key_with_modifier_common_alt_modifier_chars() { let keyvec = vec![ - Key::Alt(CharOrArrow::Char('a')), - Key::Alt(CharOrArrow::Char('b')), - Key::Alt(CharOrArrow::Char('c')), - Key::Alt(CharOrArrow::Char('d')), + KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('b')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('c')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('d')).with_alt_modifier(), ]; let palette = get_palette(); @@ -801,10 +782,10 @@ pub mod tests { #[test] fn style_key_with_modifier_common_alt_modifier_with_special_group_all_arrows() { let keyvec = vec![ - Key::Alt(CharOrArrow::Direction(Direction::Left)), - Key::Alt(CharOrArrow::Direction(Direction::Down)), - Key::Alt(CharOrArrow::Direction(Direction::Up)), - Key::Alt(CharOrArrow::Direction(Direction::Right)), + KeyWithModifier::new(BareKey::Left).with_alt_modifier(), + KeyWithModifier::new(BareKey::Down).with_alt_modifier(), + KeyWithModifier::new(BareKey::Up).with_alt_modifier(), + KeyWithModifier::new(BareKey::Right).with_alt_modifier(), ]; let palette = get_palette(); @@ -817,32 +798,32 @@ pub mod tests { #[test] fn style_key_with_modifier_ctrl_alt_char_mixed() { let keyvec = vec![ - Key::Alt(CharOrArrow::Char('a')), - Key::Ctrl('b'), - Key::Char('c'), + KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier(), + KeyWithModifier::new(BareKey::Char('c')), ]; let palette = get_palette(); let ret = style_key_with_modifier(&keyvec, &palette, None); let ret = unstyle(&ANSIStrings(&ret)); - assert_eq!(ret, "".to_string()) + assert_eq!(ret, "".to_string()) } #[test] fn style_key_with_modifier_unprintables() { let keyvec = vec![ - Key::Backspace, - Key::Char('\n'), - Key::Char(' '), - Key::Char('\t'), - Key::PageDown, - Key::Delete, - Key::Home, - Key::End, - Key::Insert, - Key::BackTab, - Key::Esc, + KeyWithModifier::new(BareKey::Backspace), + KeyWithModifier::new(BareKey::Enter), + KeyWithModifier::new(BareKey::Char(' ')), + KeyWithModifier::new(BareKey::Tab), + KeyWithModifier::new(BareKey::PageDown), + KeyWithModifier::new(BareKey::Delete), + KeyWithModifier::new(BareKey::Home), + KeyWithModifier::new(BareKey::End), + KeyWithModifier::new(BareKey::Insert), + KeyWithModifier::new(BareKey::Tab), + KeyWithModifier::new(BareKey::Esc), ]; let palette = get_palette(); @@ -857,7 +838,11 @@ pub mod tests { #[test] 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 ret = style_key_with_modifier(&keyvec, &palette, None); @@ -869,9 +854,9 @@ pub mod tests { #[test] fn style_key_with_modifier_unprintables_with_common_alt_modifier() { let keyvec = vec![ - Key::Alt(CharOrArrow::Char('\n')), - Key::Alt(CharOrArrow::Char(' ')), - Key::Alt(CharOrArrow::Char('\t')), + KeyWithModifier::new(BareKey::Enter).with_alt_modifier(), + KeyWithModifier::new(BareKey::Char(' ')).with_alt_modifier(), + KeyWithModifier::new(BareKey::Tab).with_alt_modifier(), ]; let palette = get_palette(); diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 53fbc6ec..63730aa4 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -15,7 +15,7 @@ use crate::{ fn full_length_shortcut( is_first_shortcut: bool, - key: Vec, + key: Vec, action: &str, palette: Palette, ) -> LinePart { @@ -59,7 +59,12 @@ fn locked_interface_indication(palette: Palette) -> LinePart { } } -fn add_shortcut(help: &ModeInfo, linepart: &LinePart, text: &str, keys: Vec) -> LinePart { +fn add_shortcut( + help: &ModeInfo, + linepart: &LinePart, + text: &str, + keys: Vec, +) -> LinePart { let shortcut = if linepart.len == 0 { full_length_shortcut(true, keys, text, help.style.colors) } 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 // for humans. #[rustfmt::skip] -fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { +fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { use Action as A; use InputMode as IM; use Direction as Dir; @@ -119,8 +124,8 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { // 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! let to_normal_keys = action_key(&old_keymap, &[TO_NORMAL]); - let to_normal_key = if to_normal_keys.contains(&Key::Char('\n')) { - vec![Key::Char('\n')] + let to_normal_key = if to_normal_keys.contains(&KeyWithModifier::new(BareKey::Enter)) { + vec![KeyWithModifier::new(BareKey::Enter)] } else { // 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() @@ -164,11 +169,11 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { // RightArrow. // FIXME: So for lack of a better idea we just check this case manually here. let old_keymap = mi.get_mode_keybinds(); - let focus_keys_full: Vec = action_key_group(&old_keymap, + let focus_keys_full: Vec = action_key_group(&old_keymap, &[&[A::GoToPreviousTab], &[A::GoToNextTab]]); - let focus_keys = if focus_keys_full.contains(&Key::Left) - && focus_keys_full.contains(&Key::Right) { - vec![Key::Left, Key::Right] + let focus_keys = if focus_keys_full.contains(&KeyWithModifier::new(BareKey::Left)) + && focus_keys_full.contains(&KeyWithModifier::new(BareKey::Right)) { + vec![KeyWithModifier::new(BareKey::Left), KeyWithModifier::new(BareKey::Right)] } else { 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)]) .first() - .unwrap_or(&Key::Char('?')) + .unwrap_or(&KeyWithModifier::new(BareKey::Char('?'))) ); let plus = ", "; let p_left_separator = "<"; @@ -440,7 +445,7 @@ pub fn floating_panes_are_visible(mode_info: &ModeInfo) -> LinePart { &[Action::ToggleFloatingPanes, TO_NORMAL] ) .first() - .unwrap_or(&Key::Char('?')) + .unwrap_or(&KeyWithModifier::new(BareKey::Char('?'))) ); let p_right_separator = "> "; let to_hide = "to hide."; @@ -560,7 +565,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -571,7 +576,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(true, keyvec, "Foobar", palette); @@ -594,7 +599,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -605,7 +610,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -616,7 +621,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -627,7 +632,7 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -638,7 +643,11 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -649,18 +658,26 @@ mod tests { #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); let ret = unstyle(ret); - assert_eq!(ret, " / Foobar"); + assert_eq!(ret, " / Foobar"); } #[test] 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 ret = full_length_shortcut(false, keyvec, "Foobar", palette); @@ -678,14 +695,32 @@ mod tests { keybinds: vec![( InputMode::Pane, 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], ), ], @@ -710,14 +745,32 @@ mod tests { keybinds: vec![( InputMode::Pane, 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], ), ], @@ -738,13 +791,34 @@ mod tests { keybinds: vec![( InputMode::Pane, vec![ - (Key::Ctrl('a'), vec![Action::MoveFocus(Direction::Left)]), - (Key::Ctrl('\n'), vec![Action::MoveFocus(Direction::Down)]), - (Key::Ctrl('1'), vec![Action::MoveFocus(Direction::Up)]), - (Key::Ctrl(' '), vec![Action::MoveFocus(Direction::Right)]), - (Key::Backspace, vec![Action::NewPane(None, None), TO_NORMAL]), - (Key::Esc, vec![Action::CloseFocus, TO_NORMAL]), - (Key::End, vec![Action::ToggleFocusFullscreen, TO_NORMAL]), + ( + KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier(), + vec![Action::MoveFocus(Direction::Left)], + ), + ( + KeyWithModifier::new(BareKey::Enter).with_ctrl_modifier(), + 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() diff --git a/default-plugins/status-bar/src/tip/data/quicknav.rs b/default-plugins/status-bar/src/tip/data/quicknav.rs index 318fe702..9d66754e 100644 --- a/default-plugins/status-bar/src/tip/data/quicknav.rs +++ b/default-plugins/status-bar/src/tip/data/quicknav.rs @@ -76,10 +76,10 @@ fn add_keybinds(help: &ModeInfo) -> Keygroups { &[Action::Resize(Resize::Decrease, None)], ], ); - if resize_keys.contains(&Key::Alt(CharOrArrow::Char('='))) - && resize_keys.contains(&Key::Alt(CharOrArrow::Char('+'))) + if resize_keys.contains(&KeyWithModifier::new(BareKey::Char('=')).with_alt_modifier()) + && 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() { vec![Style::new().bold().paint("UNBOUND")] diff --git a/default-plugins/strider/src/main.rs b/default-plugins/strider/src/main.rs index ce9a5273..b1c2308b 100644 --- a/default-plugins/strider/src/main.rs +++ b/default-plugins/strider/src/main.rs @@ -63,42 +63,47 @@ impl ZellijPlugin for State { self.update_files(paths); should_render = true; }, - Event::Key(key) => match key { - Key::Char(character) if character != '\n' => { + Event::Key(key) => match key.bare_key { + BareKey::Char(character) if key.has_no_modifiers() => { self.update_search_term(character); should_render = true; }, - Key::Backspace => { + BareKey::Backspace if key.has_no_modifiers() => { self.handle_backspace(); should_render = true; }, - Key::Esc | Key::Ctrl('c') => { + BareKey::Esc if key.has_no_modifiers() => { self.clear_search_term_or_descend(); 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(); should_render = true; }, - Key::Down => { + BareKey::Down if key.has_no_modifiers() => { self.move_selection_down(); 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(); }, - Key::Char('\n') => { + BareKey::Enter if key.has_no_modifiers() => { self.open_selected_path(); }, - Key::Right | Key::BackTab => { + BareKey::Right | BareKey::Tab if key.has_no_modifiers() => { self.traverse_dir(); should_render = true; }, - Key::Left => { + BareKey::Left if key.has_no_modifiers() => { self.descend_to_previous_path(); should_render = true; }, - Key::Ctrl('e') => { + BareKey::Char('e') if key.has_modifiers(&[KeyModifier::Ctrl]) => { should_render = true; self.toggle_hidden_files(); refresh_directory(&self.file_list_view.path); diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index f2dd0933..6b1c12c6 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -2226,6 +2226,7 @@ pub fn send_command_through_the_cli() { // cursor does not appear in // suspend_start panes { + std::thread::sleep(std::time::Duration::from_millis(100)); remote_terminal.send_key(&SPACE); // run script - here we use SPACE // instead of the default ENTER because // sending ENTER over SSH can be a little @@ -2243,6 +2244,7 @@ pub fn send_command_through_the_cli() { if remote_terminal.snapshot_contains("") && 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 // instead of the default ENTER because // sending ENTER over SSH can be a little diff --git a/src/tests/e2e/remote_runner.rs b/src/tests/e2e/remote_runner.rs index 2f93d5b6..ab2c4a9e 100644 --- a/src/tests/e2e/remote_runner.rs +++ b/src/tests/e2e/remote_runner.rs @@ -237,6 +237,7 @@ fn read_from_channel( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_output = TerminalPane::new( 0, pane_geom, @@ -253,6 +254,7 @@ fn read_from_channel( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index loop { if !should_keep_running.load(Ordering::SeqCst) { diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap index fd55ac42..5308c782 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1048 +assertion_line: 1171 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. + (FLOATING PANES VISIBLE): Press Ctrl p, to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap index f3677360..8c0477f8 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__quit_and_resurrect_session_with_viewport_serialization.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1109 +assertion_line: 1232 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  @@ -26,4 +26,4 @@ expression: last_snapshot │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. + (FLOATING PANES VISIBLE): Press Ctrl p, to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap index 974a63bd..a0fe5fbd 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__status_bar_loads_custom_keybindings.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1011 +assertion_line: 1266 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + LOCK  PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: UNBOUND => open new pane. UNBOUND => navigate between panes. UNBOUND => increase/decrease pane size. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap index e76aa32a..3007e299 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1724 +assertion_line: 1984 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -26,4 +26,4 @@ expression: last_snapshot │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  - (FLOATING PANES VISIBLE): Press Ctrl+p, to hide. + (FLOATING PANES VISIBLE): Press Ctrl p, to hide. diff --git a/zellij-client/src/input_handler.rs b/zellij-client/src/input_handler.rs index 058b2bbc..b00d1280 100644 --- a/zellij-client/src/input_handler.rs +++ b/zellij-client/src/input_handler.rs @@ -5,7 +5,7 @@ use crate::{ }; use zellij_utils::{ channels::{Receiver, SenderWithContext, OPENCALLS}, - data::{InputMode, Key}, + data::{InputMode, KeyWithModifier}, errors::{ContextType, ErrorContext, FatalError}, input::{ actions::Action, @@ -96,7 +96,7 @@ impl InputHandler { &raw_bytes, Some((&self.config.keybinds, &self.mode)), ); - self.handle_key(&key, raw_bytes); + self.handle_key(&key, raw_bytes, false); }, InputEvent::Mouse(mouse_event) => { let mouse_event = @@ -106,15 +106,15 @@ impl InputHandler { InputEvent::Paste(pasted_text) => { if self.mode == InputMode::Normal || self.mode == InputMode::Locked { self.dispatch_action( - Action::Write(bracketed_paste_start.clone()), + Action::Write(None, bracketed_paste_start.clone(), false), None, ); self.dispatch_action( - Action::Write(pasted_text.as_bytes().to_vec()), + Action::Write(None, pasted_text.as_bytes().to_vec(), false), None, ); self.dispatch_action( - Action::Write(bracketed_paste_end.clone()), + Action::Write(None, bracketed_paste_end.clone(), false), 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)) => { self.mode = input_mode; }, @@ -168,11 +174,19 @@ impl InputHandler { } } } - fn handle_key(&mut self, key: &Key, raw_bytes: Vec) { + fn handle_key( + &mut self, + key: &KeyWithModifier, + raw_bytes: Vec, + is_kitty_keyboard_protocol: bool, + ) { let keybinds = &self.config.keybinds; - for action in - keybinds.get_actions_for_key_in_mode_or_default_action(&self.mode, key, raw_bytes) - { + for action in keybinds.get_actions_for_key_in_mode_or_default_action( + &self.mode, + key, + raw_bytes, + is_kitty_keyboard_protocol, + ) { let should_exit = self.dispatch_action(action, None); if should_exit { self.should_exit = true; diff --git a/zellij-client/src/keyboard_parser.rs b/zellij-client/src/keyboard_parser.rs new file mode 100644 index 00000000..73b36d06 --- /dev/null +++ b/zellij-client/src/keyboard_parser.rs @@ -0,0 +1,1779 @@ +// for more info, please see: https://sw.kovidgoyal.net/kitty/keyboard-protocol +use zellij_utils::data::KeyWithModifier; + +#[derive(Debug)] +enum KittyKeysParsingState { + Ground, + ReceivedEscapeCharacter, + ParsingNumber, + ParsingModifiers, + DoneParsingWithU, + DoneParsingWithTilde, +} + +#[derive(Debug)] +pub struct KittyKeyboardParser { + state: KittyKeysParsingState, + number_bytes: Vec, + modifier_bytes: Vec, +} + +impl KittyKeyboardParser { + pub fn new() -> Self { + KittyKeyboardParser { + state: KittyKeysParsingState::Ground, + number_bytes: vec![], + modifier_bytes: vec![], + } + } + pub fn parse(&mut self, buffer: &[u8]) -> Option { + for byte in buffer { + if !self.advance(*byte) { + return None; + } + } + match self.state { + KittyKeysParsingState::DoneParsingWithU => { + // CSI number ; modifiers u + KeyWithModifier::from_bytes_with_u(&self.number_bytes, &self.modifier_bytes) + }, + KittyKeysParsingState::DoneParsingWithTilde => { + // CSI number ; modifiers ~ + KeyWithModifier::from_bytes_with_tilde(&self.number_bytes, &self.modifier_bytes) + }, + KittyKeysParsingState::ParsingModifiers => { + // CSI 1; modifiers [ABCDEFHPQS] + match self.modifier_bytes.pop() { + Some(last_modifier) => KeyWithModifier::from_bytes_with_no_ending_byte( + &[last_modifier], + &self.modifier_bytes, + ), + None => None, + } + }, + KittyKeysParsingState::ParsingNumber => { + KeyWithModifier::from_bytes_with_no_ending_byte( + &self.number_bytes, + &self.modifier_bytes, + ) + }, + _ => None, + } + } + pub fn advance(&mut self, byte: u8) -> bool { + // returns false if we failed parsing + match (&self.state, byte) { + (KittyKeysParsingState::Ground, 0x1b | 0x5b) => { + self.state = KittyKeysParsingState::ReceivedEscapeCharacter; + }, + (KittyKeysParsingState::ReceivedEscapeCharacter, 91) => { + self.state = KittyKeysParsingState::ParsingNumber; + }, + (KittyKeysParsingState::ParsingNumber, 59) => { + // semicolon + if &self.number_bytes == &[49] { + self.number_bytes.clear(); + } + self.state = KittyKeysParsingState::ParsingModifiers; + }, + ( + KittyKeysParsingState::ParsingNumber | KittyKeysParsingState::ParsingModifiers, + 117, + ) => { + // u + self.state = KittyKeysParsingState::DoneParsingWithU; + }, + ( + KittyKeysParsingState::ParsingNumber | KittyKeysParsingState::ParsingModifiers, + 126, + ) => { + // ~ + self.state = KittyKeysParsingState::DoneParsingWithTilde; + }, + (KittyKeysParsingState::ParsingNumber, _) => { + self.number_bytes.push(byte); + }, + (KittyKeysParsingState::ParsingModifiers, _) => { + self.modifier_bytes.push(byte); + }, + (_, _) => { + return false; + }, + } + true + } +} + +#[test] +pub fn can_parse_bare_keys() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('a'))), + "Can parse a bare 'a' keypress" + ); + let key = "\u{1b}[49u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('1'))), + "Can parse a bare '1' keypress" + ); + let key = "\u{1b}[27u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Esc)), + "Can parse a bare 'ESC' keypress" + ); + let key = "\u{1b}[13u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Enter)), + "Can parse a bare 'ENTER' keypress" + ); + let key = "\u{1b}[9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Tab)), + "Can parse a bare 'Tab' keypress" + ); + let key = "\u{1b}[127u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Backspace)), + "Can parse a bare 'Backspace' keypress" + ); + let key = "\u{1b}[57358u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::CapsLock)), + "Can parse a bare 'CapsLock' keypress" + ); + let key = "\u{1b}[57359u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::ScrollLock)), + "Can parse a bare 'ScrollLock' keypress" + ); + let key = "\u{1b}[57360u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::NumLock)), + "Can parse a bare 'NumLock' keypress" + ); + let key = "\u{1b}[57361u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PrintScreen)), + "Can parse a bare 'PrintScreen' keypress" + ); + let key = "\u{1b}[57362u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Pause)), + "Can parse a bare 'Pause' keypress" + ); + let key = "\u{1b}[57363u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Menu)), + "Can parse a bare 'Menu' keypress" + ); + + let key = "\u{1b}[2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Insert)), + "Can parse a bare 'Insert' keypress" + ); + let key = "\u{1b}[3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Delete)), + "Can parse a bare 'Delete' keypress" + ); + let key = "\u{1b}[5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageUp)), + "Can parse a bare 'PageUp' keypress" + ); + let key = "\u{1b}[6~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageDown)), + "Can parse a bare 'PageDown' keypress" + ); + let key = "\u{1b}[7~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home)), + "Can parse a bare 'Home' keypress" + ); + let key = "\u{1b}[8~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End)), + "Can parse a bare 'End' keypress" + ); + let key = "\u{1b}[11~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1))), + "Can parse a bare 'F1' keypress" + ); + let key = "\u{1b}[12~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2))), + "Can parse a bare 'F2' keypress" + ); + let key = "\u{1b}[13~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(3))), + "Can parse a bare 'F3' keypress" + ); + let key = "\u{1b}[14~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4))), + "Can parse a bare 'F4' keypress" + ); + let key = "\u{1b}[15~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(5))), + "Can parse a bare 'F5' keypress" + ); + let key = "\u{1b}[17~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(6))), + "Can parse a bare 'F6' keypress" + ); + let key = "\u{1b}[18~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(7))), + "Can parse a bare 'F7' keypress" + ); + let key = "\u{1b}[19~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(8))), + "Can parse a bare 'F8' keypress" + ); + let key = "\u{1b}[20~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(9))), + "Can parse a bare 'F9' keypress" + ); + let key = "\u{1b}[21~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(10))), + "Can parse a bare 'F10' keypress" + ); + let key = "\u{1b}[23~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(11))), + "Can parse a bare 'F11' keypress" + ); + let key = "\u{1b}[24~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(12))), + "Can parse a bare 'F12' keypress" + ); + let key = "\u{1b}[D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Left)), + "Can parse a bare 'Left' keypress" + ); + let key = "\u{1b}[C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Right)), + "Can parse a bare 'Right' keypress" + ); + let key = "\u{1b}[A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Up)), + "Can parse a bare 'Up' keypress" + ); + let key = "\u{1b}[B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Down)), + "Can parse a bare 'Down' keypress" + ); + let key = "\u{1b}[H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home)), + "Can parse a bare 'Home' keypress" + ); + let key = "\u{1b}[F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End)), + "Can parse a bare 'End' keypress" + ); + let key = "\u{1b}[P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1))), + "Can parse a bare 'F1 (alternate)' keypress" + ); + let key = "\u{1b}[Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2))), + "Can parse a bare 'F2 (alternate)' keypress" + ); + let key = "\u{1b}[S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4))), + "Can parse a bare 'F4 (alternate)' keypress" + ); +} + +#[test] +pub fn can_parse_keys_with_shift_modifier() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('a')).with_shift_modifier()), + "Can parse a bare 'a' keypress with shift" + ); + let key = "\u{1b}[49;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('1')).with_shift_modifier()), + "Can parse a bare '1' keypress with shift" + ); + let key = "\u{1b}[27;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Esc).with_shift_modifier()), + "Can parse a bare 'ESC' keypress with shift" + ); + let key = "\u{1b}[13;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Enter).with_shift_modifier()), + "Can parse a bare 'ENTER' keypress with shift" + ); + let key = "\u{1b}[9;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Tab).with_shift_modifier()), + "Can parse a bare 'Tab' keypress with shift" + ); + let key = "\u{1b}[127;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Backspace).with_shift_modifier()), + "Can parse a bare 'Backspace' keypress with shift" + ); + let key = "\u{1b}[57358;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::CapsLock).with_shift_modifier()), + "Can parse a bare 'CapsLock' keypress with shift" + ); + let key = "\u{1b}[57359;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::ScrollLock).with_shift_modifier()), + "Can parse a bare 'ScrollLock' keypress with shift" + ); + let key = "\u{1b}[57360;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::NumLock).with_shift_modifier()), + "Can parse a bare 'NumLock' keypress with shift" + ); + let key = "\u{1b}[57361;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PrintScreen).with_shift_modifier()), + "Can parse a bare 'PrintScreen' keypress with shift" + ); + let key = "\u{1b}[57362;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Pause).with_shift_modifier()), + "Can parse a bare 'Pause' keypress with shift" + ); + let key = "\u{1b}[57363;2u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Menu).with_shift_modifier()), + "Can parse a bare 'Menu' keypress with shift" + ); + + let key = "\u{1b}[2;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Insert).with_shift_modifier()), + "Can parse a bare 'Insert' keypress with shift" + ); + let key = "\u{1b}[3;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Delete).with_shift_modifier()), + "Can parse a bare 'Delete' keypress with shift" + ); + let key = "\u{1b}[5;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageUp).with_shift_modifier()), + "Can parse a bare 'PageUp' keypress with shift" + ); + let key = "\u{1b}[6;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageDown).with_shift_modifier()), + "Can parse a bare 'PageDown' keypress with shift" + ); + let key = "\u{1b}[7;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_shift_modifier()), + "Can parse a bare 'Home' keypress with shift" + ); + let key = "\u{1b}[8;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_shift_modifier()), + "Can parse a bare 'End' keypress with shift" + ); + let key = "\u{1b}[11;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_shift_modifier()), + "Can parse a bare 'F1' keypress with shift" + ); + let key = "\u{1b}[12;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_shift_modifier()), + "Can parse a bare 'F2' keypress with shift" + ); + let key = "\u{1b}[13;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(3)).with_shift_modifier()), + "Can parse a bare 'F3' keypress with shift" + ); + let key = "\u{1b}[14;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_shift_modifier()), + "Can parse a bare 'F4' keypress with shift" + ); + let key = "\u{1b}[15;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(5)).with_shift_modifier()), + "Can parse a bare 'F5' keypress with shift" + ); + let key = "\u{1b}[17;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(6)).with_shift_modifier()), + "Can parse a bare 'F6' keypress with shift" + ); + let key = "\u{1b}[18;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(7)).with_shift_modifier()), + "Can parse a bare 'F7' keypress with shift" + ); + let key = "\u{1b}[19;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(8)).with_shift_modifier()), + "Can parse a bare 'F8' keypress with shift" + ); + let key = "\u{1b}[20;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(9)).with_shift_modifier()), + "Can parse a bare 'F9' keypress with shift" + ); + let key = "\u{1b}[21;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(10)).with_shift_modifier()), + "Can parse a bare 'F10' keypress with shift" + ); + let key = "\u{1b}[23;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(11)).with_shift_modifier()), + "Can parse a bare 'F11' keypress with shift" + ); + let key = "\u{1b}[24;2~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(12)).with_shift_modifier()), + "Can parse a bare 'F12' keypress with shift" + ); + let key = "\u{1b}[1;2D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Left).with_shift_modifier()), + "Can parse a bare 'Left' keypress with shift" + ); + let key = "\u{1b}[1;2C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Right).with_shift_modifier()), + "Can parse a bare 'Right' keypress with shift" + ); + let key = "\u{1b}[1;2A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Up).with_shift_modifier()), + "Can parse a bare 'Up' keypress with shift" + ); + let key = "\u{1b}[1;2B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Down).with_shift_modifier()), + "Can parse a bare 'Down' keypress with shift" + ); + let key = "\u{1b}[1;2H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_shift_modifier()), + "Can parse a bare 'Home' keypress with shift" + ); + let key = "\u{1b}[1;2F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_shift_modifier()), + "Can parse a bare 'End' keypress with shift" + ); + let key = "\u{1b}[1;2P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_shift_modifier()), + "Can parse a bare 'F1 (alternate)' keypress with shift" + ); + let key = "\u{1b}[1;2Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_shift_modifier()), + "Can parse a bare 'F2 (alternate)' keypress with shift" + ); + let key = "\u{1b}[1;2S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_shift_modifier()), + "Can parse a bare 'F4 (alternate)' keypress with shift" + ); +} + +#[test] +pub fn can_parse_keys_with_alt_modifier() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('a')).with_alt_modifier()), + "Can parse a bare 'a' keypress with alt" + ); + let key = "\u{1b}[49;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('1')).with_alt_modifier()), + "Can parse a bare '1' keypress with alt" + ); + let key = "\u{1b}[27;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Esc).with_alt_modifier()), + "Can parse a bare 'ESC' keypress with alt" + ); + let key = "\u{1b}[13;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Enter).with_alt_modifier()), + "Can parse a bare 'ENTER' keypress with alt" + ); + let key = "\u{1b}[9;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Tab).with_alt_modifier()), + "Can parse a bare 'Tab' keypress with alt" + ); + let key = "\u{1b}[127;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Backspace).with_alt_modifier()), + "Can parse a bare 'Backspace' keypress with alt" + ); + let key = "\u{1b}[57358;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::CapsLock).with_alt_modifier()), + "Can parse a bare 'CapsLock' keypress with alt" + ); + let key = "\u{1b}[57359;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::ScrollLock).with_alt_modifier()), + "Can parse a bare 'ScrollLock' keypress with alt" + ); + let key = "\u{1b}[57360;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::NumLock).with_alt_modifier()), + "Can parse a bare 'NumLock' keypress with alt" + ); + let key = "\u{1b}[57361;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PrintScreen).with_alt_modifier()), + "Can parse a bare 'PrintScreen' keypress with alt" + ); + let key = "\u{1b}[57362;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Pause).with_alt_modifier()), + "Can parse a bare 'Pause' keypress with alt" + ); + let key = "\u{1b}[57363;3u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Menu).with_alt_modifier()), + "Can parse a bare 'Menu' keypress with alt" + ); + + let key = "\u{1b}[2;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Insert).with_alt_modifier()), + "Can parse a bare 'Insert' keypress with alt" + ); + let key = "\u{1b}[3;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Delete).with_alt_modifier()), + "Can parse a bare 'Delete' keypress with alt" + ); + let key = "\u{1b}[5;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageUp).with_alt_modifier()), + "Can parse a bare 'PageUp' keypress with alt" + ); + let key = "\u{1b}[6;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageDown).with_alt_modifier()), + "Can parse a bare 'PageDown' keypress with alt" + ); + let key = "\u{1b}[7;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_alt_modifier()), + "Can parse a bare 'Home' keypress with alt" + ); + let key = "\u{1b}[8;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_alt_modifier()), + "Can parse a bare 'End' keypress with alt" + ); + let key = "\u{1b}[11;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_alt_modifier()), + "Can parse a bare 'F1' keypress with alt" + ); + let key = "\u{1b}[12;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_alt_modifier()), + "Can parse a bare 'F2' keypress with alt" + ); + let key = "\u{1b}[13;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(3)).with_alt_modifier()), + "Can parse a bare 'F3' keypress with alt" + ); + let key = "\u{1b}[14;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_alt_modifier()), + "Can parse a bare 'F4' keypress with alt" + ); + let key = "\u{1b}[15;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(5)).with_alt_modifier()), + "Can parse a bare 'F5' keypress with alt" + ); + let key = "\u{1b}[17;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(6)).with_alt_modifier()), + "Can parse a bare 'F6' keypress with alt" + ); + let key = "\u{1b}[18;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(7)).with_alt_modifier()), + "Can parse a bare 'F7' keypress with alt" + ); + let key = "\u{1b}[19;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(8)).with_alt_modifier()), + "Can parse a bare 'F8' keypress with alt" + ); + let key = "\u{1b}[20;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(9)).with_alt_modifier()), + "Can parse a bare 'F9' keypress with alt" + ); + let key = "\u{1b}[21;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(10)).with_alt_modifier()), + "Can parse a bare 'F10' keypress with alt" + ); + let key = "\u{1b}[23;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(11)).with_alt_modifier()), + "Can parse a bare 'F11' keypress with alt" + ); + let key = "\u{1b}[24;3~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(12)).with_alt_modifier()), + "Can parse a bare 'F12' keypress with alt" + ); + let key = "\u{1b}[1;3D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Left).with_alt_modifier()), + "Can parse a bare 'Left' keypress with alt" + ); + let key = "\u{1b}[1;3C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Right).with_alt_modifier()), + "Can parse a bare 'Right' keypress with alt" + ); + let key = "\u{1b}[1;3A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Up).with_alt_modifier()), + "Can parse a bare 'Up' keypress with alt" + ); + let key = "\u{1b}[1;3B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Down).with_alt_modifier()), + "Can parse a bare 'Down' keypress with alt" + ); + let key = "\u{1b}[1;3H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_alt_modifier()), + "Can parse a bare 'Home' keypress with alt" + ); + let key = "\u{1b}[1;3F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_alt_modifier()), + "Can parse a bare 'End' keypress with alt" + ); + let key = "\u{1b}[1;3P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_alt_modifier()), + "Can parse a bare 'F1 (alternate)' keypress with alt" + ); + let key = "\u{1b}[1;3Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_alt_modifier()), + "Can parse a bare 'F2 (alternate)' keypress with alt" + ); + let key = "\u{1b}[1;3S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_alt_modifier()), + "Can parse a bare 'F4 (alternate)' keypress with alt" + ); +} + +#[test] +pub fn can_parse_keys_with_ctrl_modifier() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier()), + "Can parse a bare 'a' keypress with ctrl" + ); + let key = "\u{1b}[49;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('1')).with_ctrl_modifier()), + "Can parse a bare '1' keypress with ctrl" + ); + let key = "\u{1b}[27;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Esc).with_ctrl_modifier()), + "Can parse a bare 'ESC' keypress with ctrl" + ); + let key = "\u{1b}[13;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Enter).with_ctrl_modifier()), + "Can parse a bare 'ENTER' keypress with ctrl" + ); + let key = "\u{1b}[9;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Tab).with_ctrl_modifier()), + "Can parse a bare 'Tab' keypress with ctrl" + ); + let key = "\u{1b}[127;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Backspace).with_ctrl_modifier()), + "Can parse a bare 'Backspace' keypress with ctrl" + ); + let key = "\u{1b}[57358;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::CapsLock).with_ctrl_modifier()), + "Can parse a bare 'CapsLock' keypress with ctrl" + ); + let key = "\u{1b}[57359;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::ScrollLock).with_ctrl_modifier()), + "Can parse a bare 'ScrollLock' keypress with ctrl" + ); + let key = "\u{1b}[57360;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::NumLock).with_ctrl_modifier()), + "Can parse a bare 'NumLock' keypress with ctrl" + ); + let key = "\u{1b}[57361;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PrintScreen).with_ctrl_modifier()), + "Can parse a bare 'PrintScreen' keypress with ctrl" + ); + let key = "\u{1b}[57362;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Pause).with_ctrl_modifier()), + "Can parse a bare 'Pause' keypress with ctrl" + ); + let key = "\u{1b}[57363;5u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Menu).with_ctrl_modifier()), + "Can parse a bare 'Menu' keypress with ctrl" + ); + + let key = "\u{1b}[2;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Insert).with_ctrl_modifier()), + "Can parse a bare 'Insert' keypress with ctrl" + ); + let key = "\u{1b}[3;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Delete).with_ctrl_modifier()), + "Can parse a bare 'Delete' keypress with ctrl" + ); + let key = "\u{1b}[5;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageUp).with_ctrl_modifier()), + "Can parse a bare 'PageUp' keypress with ctrl" + ); + let key = "\u{1b}[6;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageDown).with_ctrl_modifier()), + "Can parse a bare 'PageDown' keypress with ctrl" + ); + let key = "\u{1b}[7;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_ctrl_modifier()), + "Can parse a bare 'Home' keypress with ctrl" + ); + let key = "\u{1b}[8;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_ctrl_modifier()), + "Can parse a bare 'End' keypress with ctrl" + ); + let key = "\u{1b}[11;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_ctrl_modifier()), + "Can parse a bare 'F1' keypress with ctrl" + ); + let key = "\u{1b}[12;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_ctrl_modifier()), + "Can parse a bare 'F2' keypress with ctrl" + ); + let key = "\u{1b}[13;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(3)).with_ctrl_modifier()), + "Can parse a bare 'F3' keypress with ctrl" + ); + let key = "\u{1b}[14;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_ctrl_modifier()), + "Can parse a bare 'F4' keypress with ctrl" + ); + let key = "\u{1b}[15;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(5)).with_ctrl_modifier()), + "Can parse a bare 'F5' keypress with ctrl" + ); + let key = "\u{1b}[17;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(6)).with_ctrl_modifier()), + "Can parse a bare 'F6' keypress with ctrl" + ); + let key = "\u{1b}[18;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(7)).with_ctrl_modifier()), + "Can parse a bare 'F7' keypress with ctrl" + ); + let key = "\u{1b}[19;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(8)).with_ctrl_modifier()), + "Can parse a bare 'F8' keypress with ctrl" + ); + let key = "\u{1b}[20;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(9)).with_ctrl_modifier()), + "Can parse a bare 'F9' keypress with ctrl" + ); + let key = "\u{1b}[21;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(10)).with_ctrl_modifier()), + "Can parse a bare 'F10' keypress with ctrl" + ); + let key = "\u{1b}[23;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(11)).with_ctrl_modifier()), + "Can parse a bare 'F11' keypress with ctrl" + ); + let key = "\u{1b}[24;5~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(12)).with_ctrl_modifier()), + "Can parse a bare 'F12' keypress with ctrl" + ); + let key = "\u{1b}[1;5D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Left).with_ctrl_modifier()), + "Can parse a bare 'Left' keypress with ctrl" + ); + let key = "\u{1b}[1;5C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Right).with_ctrl_modifier()), + "Can parse a bare 'Right' keypress with ctrl" + ); + let key = "\u{1b}[1;5A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Up).with_ctrl_modifier()), + "Can parse a bare 'Up' keypress with ctrl" + ); + let key = "\u{1b}[1;5B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Down).with_ctrl_modifier()), + "Can parse a bare 'Down' keypress with ctrl" + ); + let key = "\u{1b}[1;5H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_ctrl_modifier()), + "Can parse a bare 'Home' keypress with ctrl" + ); + let key = "\u{1b}[1;5F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_ctrl_modifier()), + "Can parse a bare 'End' keypress with ctrl" + ); + let key = "\u{1b}[1;5P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_ctrl_modifier()), + "Can parse a bare 'F1 (ctrlernate)' keypress with ctrl" + ); + let key = "\u{1b}[1;5Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_ctrl_modifier()), + "Can parse a bare 'F2 (ctrlernate)' keypress with ctrl" + ); + let key = "\u{1b}[1;5S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_ctrl_modifier()), + "Can parse a bare 'F4 (ctrlernate)' keypress with ctrl" + ); +} + +#[test] +pub fn can_parse_keys_with_super_modifier() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('a')).with_super_modifier()), + "Can parse a bare 'a' keypress with super" + ); + let key = "\u{1b}[49;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Char('1')).with_super_modifier()), + "Can parse a bare '1' keypress with super" + ); + let key = "\u{1b}[27;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Esc).with_super_modifier()), + "Can parse a bare 'ESC' keypress with super" + ); + let key = "\u{1b}[13;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Enter).with_super_modifier()), + "Can parse a bare 'ENTER' keypress with super" + ); + let key = "\u{1b}[9;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Tab).with_super_modifier()), + "Can parse a bare 'Tab' keypress with super" + ); + let key = "\u{1b}[127;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Backspace).with_super_modifier()), + "Can parse a bare 'Backspace' keypress with super" + ); + let key = "\u{1b}[57358;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::CapsLock).with_super_modifier()), + "Can parse a bare 'CapsLock' keypress with super" + ); + let key = "\u{1b}[57359;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::ScrollLock).with_super_modifier()), + "Can parse a bare 'ScrollLock' keypress with super" + ); + let key = "\u{1b}[57360;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::NumLock).with_super_modifier()), + "Can parse a bare 'NumLock' keypress with super" + ); + let key = "\u{1b}[57361;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PrintScreen).with_super_modifier()), + "Can parse a bare 'PrintScreen' keypress with super" + ); + let key = "\u{1b}[57362;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Pause).with_super_modifier()), + "Can parse a bare 'Pause' keypress with super" + ); + let key = "\u{1b}[57363;9u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Menu).with_super_modifier()), + "Can parse a bare 'Menu' keypress with super" + ); + + let key = "\u{1b}[2;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Insert).with_super_modifier()), + "Can parse a bare 'Insert' keypress with super" + ); + let key = "\u{1b}[3;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Delete).with_super_modifier()), + "Can parse a bare 'Delete' keypress with super" + ); + let key = "\u{1b}[5;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageUp).with_super_modifier()), + "Can parse a bare 'PageUp' keypress with super" + ); + let key = "\u{1b}[6;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::PageDown).with_super_modifier()), + "Can parse a bare 'PageDown' keypress with super" + ); + let key = "\u{1b}[7;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_super_modifier()), + "Can parse a bare 'Home' keypress with super" + ); + let key = "\u{1b}[8;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_super_modifier()), + "Can parse a bare 'End' keypress with super" + ); + let key = "\u{1b}[11;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_super_modifier()), + "Can parse a bare 'F1' keypress with super" + ); + let key = "\u{1b}[12;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_super_modifier()), + "Can parse a bare 'F2' keypress with super" + ); + let key = "\u{1b}[13;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(3)).with_super_modifier()), + "Can parse a bare 'F3' keypress with super" + ); + let key = "\u{1b}[14;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_super_modifier()), + "Can parse a bare 'F4' keypress with super" + ); + let key = "\u{1b}[15;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(5)).with_super_modifier()), + "Can parse a bare 'F5' keypress with super" + ); + let key = "\u{1b}[17;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(6)).with_super_modifier()), + "Can parse a bare 'F6' keypress with super" + ); + let key = "\u{1b}[18;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(7)).with_super_modifier()), + "Can parse a bare 'F7' keypress with super" + ); + let key = "\u{1b}[19;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(8)).with_super_modifier()), + "Can parse a bare 'F8' keypress with super" + ); + let key = "\u{1b}[20;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(9)).with_super_modifier()), + "Can parse a bare 'F9' keypress with super" + ); + let key = "\u{1b}[21;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(10)).with_super_modifier()), + "Can parse a bare 'F10' keypress with super" + ); + let key = "\u{1b}[23;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(11)).with_super_modifier()), + "Can parse a bare 'F11' keypress with super" + ); + let key = "\u{1b}[24;9~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(12)).with_super_modifier()), + "Can parse a bare 'F12' keypress with super" + ); + let key = "\u{1b}[1;9D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Left).with_super_modifier()), + "Can parse a bare 'Left' keypress with super" + ); + let key = "\u{1b}[1;9C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Right).with_super_modifier()), + "Can parse a bare 'Right' keypress with super" + ); + let key = "\u{1b}[1;9A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Up).with_super_modifier()), + "Can parse a bare 'Up' keypress with super" + ); + let key = "\u{1b}[1;9B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Down).with_super_modifier()), + "Can parse a bare 'Down' keypress with super" + ); + let key = "\u{1b}[1;9H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::Home).with_super_modifier()), + "Can parse a bare 'Home' keypress with super" + ); + let key = "\u{1b}[1;9F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::End).with_super_modifier()), + "Can parse a bare 'End' keypress with super" + ); + let key = "\u{1b}[1;9P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(1)).with_super_modifier()), + "Can parse a bare 'F1 (alternate)' keypress with super" + ); + let key = "\u{1b}[1;9Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(2)).with_super_modifier()), + "Can parse a bare 'F2 (alternate)' keypress with super" + ); + let key = "\u{1b}[1;9S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some(KeyWithModifier::new(BareKey::F(4)).with_super_modifier()), + "Can parse a bare 'F4 (alternate)' keypress with super" + ); +} + +#[test] +pub fn can_parse_keys_with_multiple_modifiers() { + use zellij_utils::data::BareKey; + let key = "\u{1b}[97;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Char('a')) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'a' keypress with all modifiers" + ); + let key = "\u{1b}[49;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Char('1')) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare '1' keypress with all modifiers" + ); + let key = "\u{1b}[27;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Esc) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'ESC' keypress with all modifiers" + ); + let key = "\u{1b}[13;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Enter) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'ENTER' keypress with all modifiers" + ); + let key = "\u{1b}[9;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Tab) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Tab' keypress with all modifiers" + ); + let key = "\u{1b}[127;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Backspace) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Backspace' keypress with all modifiers" + ); + let key = "\u{1b}[57358;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::CapsLock) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'CapsLock' keypress with all modifiers" + ); + let key = "\u{1b}[57359;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::ScrollLock) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'ScrollLock' keypress with all modifiers" + ); + let key = "\u{1b}[57360;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::NumLock) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'NumLock' keypress with all modifiers" + ); + let key = "\u{1b}[57361;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::PrintScreen) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'PrintScreen' keypress with all modifiers" + ); + let key = "\u{1b}[57362;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Pause) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Pause' keypress with all modifiers" + ); + let key = "\u{1b}[57363;16u"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Menu) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Menu' keypress with all modifiers" + ); + + let key = "\u{1b}[2;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Insert) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Insert' keypress with all modifiers" + ); + let key = "\u{1b}[3;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Delete) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Delete' keypress with all modifiers" + ); + let key = "\u{1b}[5;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::PageUp) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'PageUp' keypress with all modifiers" + ); + let key = "\u{1b}[6;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::PageDown) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'PageDown' keypress with all modifiers" + ); + let key = "\u{1b}[7;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Home) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Home' keypress with all modifiers" + ); + let key = "\u{1b}[8;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::End) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'End' keypress with all modifiers" + ); + let key = "\u{1b}[11;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(1)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F1' keypress with all modifiers" + ); + let key = "\u{1b}[12;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(2)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F2' keypress with all modifiers" + ); + let key = "\u{1b}[13;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(3)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F3' keypress with all modifiers" + ); + let key = "\u{1b}[14;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(4)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F4' keypress with all modifiers" + ); + let key = "\u{1b}[15;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(5)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F5' keypress with all modifiers" + ); + let key = "\u{1b}[17;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(6)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F6' keypress with all modifiers" + ); + let key = "\u{1b}[18;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(7)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F7' keypress with all modifiers" + ); + let key = "\u{1b}[19;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(8)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F8' keypress with all modifiers" + ); + let key = "\u{1b}[20;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(9)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F9' keypress with all modifiers" + ); + let key = "\u{1b}[21;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(10)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F10' keypress with all modifiers" + ); + let key = "\u{1b}[23;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(11)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F11' keypress with all modifiers" + ); + let key = "\u{1b}[24;16~"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(12)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F12' keypress with all modifiers" + ); + let key = "\u{1b}[1;16D"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Left) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Left' keypress with all modifiers" + ); + let key = "\u{1b}[1;16C"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Right) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Right' keypress with all modifiers" + ); + let key = "\u{1b}[1;16A"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Up) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Up' keypress with all modifiers" + ); + let key = "\u{1b}[1;16B"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Down) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Down' keypress with all modifiers" + ); + let key = "\u{1b}[1;16H"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::Home) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'Home' keypress with all modifiers" + ); + let key = "\u{1b}[1;16F"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::End) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'End' keypress with all modifiers" + ); + let key = "\u{1b}[1;16P"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(1)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F1 (superernate)' keypress with all modifiers" + ); + let key = "\u{1b}[1;16Q"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(2)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F2 (superernate)' keypress with all modifiers" + ); + let key = "\u{1b}[1;16S"; + assert_eq!( + KittyKeyboardParser::new().parse(&key.as_bytes()), + Some( + KeyWithModifier::new(BareKey::F(4)) + .with_super_modifier() + .with_ctrl_modifier() + .with_alt_modifier() + .with_shift_modifier() + ), + "Can parse a bare 'F4 (superernate)' keypress with all modifiers" + ); +} diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index 5e2f8060..e0cd195e 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -3,6 +3,7 @@ pub mod os_input_output; pub mod cli_client; mod command_is_executing; mod input_handler; +mod keyboard_parser; pub mod old_config_converter; mod stdin_ansi_parser; mod stdin_handler; @@ -24,7 +25,7 @@ use crate::{ use zellij_utils::{ channels::{self, ChannelWithContext, SenderWithContext}, consts::{set_permissions, ZELLIJ_SOCK_DIR}, - data::{ClientId, ConnectToSession, InputMode, Style}, + data::{ClientId, ConnectToSession, InputMode, KeyWithModifier, Style}, envs, errors::{ClientContext, ContextType, ErrorInstruction}, input::{config::Config, options::Options}, @@ -152,6 +153,7 @@ impl ClientInfo { #[derive(Debug, Clone)] pub(crate) enum InputInstruction { KeyEvent(InputEvent, Vec), + KeyWithModifierEvent(KeyWithModifier, Vec), SwitchToMode(InputMode), AnsiStdinInstructions(Vec), StartedParsing, @@ -177,16 +179,21 @@ pub fn start_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 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 bracketed_paste = "\u{1b}[?2004h"; + let enter_kitty_keyboard_mode = "\u{1b}[>1u"; os_input.unset_raw_mode(0).unwrap(); if !is_a_reconnect { // 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 - // your Windows Terminal...) + // you Windows Terminal...) let _ = os_input .get_stdout_writer() .write(take_snapshot.as_bytes()) @@ -195,6 +202,12 @@ pub fn start_client( .get_stdout_writer() .write(clear_client_terminal_attributes.as_bytes()) .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()); config.env.set_vars(); @@ -299,7 +312,14 @@ pub fn start_client( let os_input = os_input.clone(); let send_input_instructions = send_input_instructions.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() @@ -533,6 +553,11 @@ pub fn start_client( info!("{}", exit_msg); os_input.unset_raw_mode(0).unwrap(); 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(); stdout.flush().unwrap(); } else { diff --git a/zellij-client/src/stdin_handler.rs b/zellij-client/src/stdin_handler.rs index eae0958b..a1235621 100644 --- a/zellij-client/src/stdin_handler.rs +++ b/zellij-client/src/stdin_handler.rs @@ -1,3 +1,4 @@ +use crate::keyboard_parser::KittyKeyboardParser; use crate::os_input_output::ClientOsApi; use crate::stdin_ansi_parser::StdinAnsiParser; use crate::InputInstruction; @@ -23,6 +24,7 @@ pub(crate) fn stdin_loop( mut os_input: Box, send_input_instructions: SenderWithContext, stdin_ansi_parser: Arc>, + explicitly_disable_kitty_keyboard_protocol: bool, ) { let mut holding_mouse = false; let mut input_parser = InputParser::new(); @@ -85,6 +87,24 @@ pub(crate) fn stdin_loop( .write_cache(ansi_stdin_events.drain(..).collect()); } 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 mut events = vec![]; input_parser.parse( diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index 3109cb10..8b532bb4 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -362,6 +362,9 @@ pub struct Grid { debug: bool, arrow_fonts: 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)] @@ -450,6 +453,7 @@ impl Grid { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_kitty_keyboard_protocol: bool, ) -> Self { let sixel_grid = SixelGrid::new(character_cell_size.clone(), sixel_image_store); // make sure this is initialized as it is used internally @@ -505,6 +509,8 @@ impl Grid { arrow_fonts, styled_underlines, lock_renders: false, + supports_kitty_keyboard_protocol: false, + explicitly_disable_kitty_keyboard_protocol, } } pub fn render_full_viewport(&mut self) { @@ -2543,6 +2549,7 @@ impl Perform for Grid { &mut self.viewport, &mut self.cursor, &mut self.sixel_grid, + &mut self.supports_kitty_keyboard_protocol, ); } self.alternate_screen_state = None; @@ -2636,6 +2643,10 @@ impl Perform for Grid { &mut self.cursor, 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 alternate_sixelgrid = std::mem::replace( &mut self.sixel_grid, @@ -2646,6 +2657,7 @@ impl Perform for Grid { current_viewport, current_cursor, alternate_sixelgrid, + current_supports_kitty_keyboard_protocol, )); self.clear_viewport_before_rendering = true; self.scrollback_buffer_lines = @@ -2825,6 +2837,27 @@ impl Perform for Grid { } } else if c == 's' { 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' { self.restore_cursor_position(); } else if c == '@' { @@ -3084,6 +3117,7 @@ pub struct AlternateScreenState { viewport: Vec, cursor: Cursor, sixel_grid: SixelGrid, + supports_kitty_keyboard_protocol: bool, } impl AlternateScreenState { pub fn new( @@ -3091,12 +3125,14 @@ impl AlternateScreenState { viewport: Vec, cursor: Cursor, sixel_grid: SixelGrid, + supports_kitty_keyboard_protocol: bool, ) -> Self { AlternateScreenState { lines_above, viewport, cursor, sixel_grid, + supports_kitty_keyboard_protocol, } } pub fn apply_contents_to( @@ -3105,11 +3141,16 @@ impl AlternateScreenState { viewport: &mut Vec, cursor: &mut Cursor, sixel_grid: &mut SixelGrid, + supports_kitty_keyboard_protocol: &mut bool, ) { std::mem::swap(&mut self.lines_above, lines_above); std::mem::swap(&mut self.viewport, viewport); std::mem::swap(&mut self.cursor, cursor); std::mem::swap(&mut self.sixel_grid, sixel_grid); + std::mem::swap( + &mut self.supports_kitty_keyboard_protocol, + supports_kitty_keyboard_protocol, + ); } } diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 0ddcd3a2..f7ad3871 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -2,7 +2,12 @@ use std::collections::{BTreeSet, HashMap}; use std::time::Instant; use crate::output::{CharacterChunk, SixelImageChunk}; -use crate::panes::{grid::Grid, sixel::SixelImageStore, LinkHandler, PaneId}; +use crate::panes::{ + grid::Grid, + sixel::SixelImageStore, + terminal_pane::{BRACKETED_PASTE_BEGIN, BRACKETED_PASTE_END}, + LinkHandler, PaneId, +}; use crate::plugins::PluginInstruction; use crate::pty::VteBytes; use crate::tab::{AdjustedInput, Pane}; @@ -13,7 +18,9 @@ use crate::ui::{ use crate::ClientId; use std::cell::RefCell; 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::position::Position; use zellij_utils::{ @@ -39,6 +46,7 @@ macro_rules! get_or_create_grid { ($self:ident, $client_id:ident) => {{ let rows = $self.get_content_rows(); 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(|| { let mut grid = Grid::new( @@ -53,6 +61,7 @@ macro_rules! get_or_create_grid { $self.debug, $self.arrow_fonts, $self.styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); grid.hide_cursor(); grid @@ -232,24 +241,54 @@ impl Pane for PluginPane { fn cursor_coordinates(&self) -> Option<(usize, usize)> { None } - fn adjust_input_to_terminal(&mut self, input_bytes: Vec) -> Option { + fn adjust_input_to_terminal( + &mut self, + key_with_modifier: &Option, + raw_input_bytes: Vec, + _raw_input_bytes_are_kitty: bool, + ) -> Option { if let Some(requesting_permissions) = &self.requesting_permissions { let permissions = requesting_permissions.permissions.clone(); - match input_bytes.as_slice() { - // Y or y - &[89] | &[121] => Some(AdjustedInput::PermissionRequestResult( - permissions, - PermissionStatus::Granted, - )), - // N or n - &[78] | &[110] => Some(AdjustedInput::PermissionRequestResult( - permissions, - PermissionStatus::Denied, - )), - _ => None, + if let Some(key_with_modifier) = key_with_modifier { + match key_with_modifier.bare_key { + BareKey::Char('y') if key_with_modifier.has_no_modifiers() => { + Some(AdjustedInput::PermissionRequestResult( + permissions, + PermissionStatus::Granted, + )) + }, + BareKey::Char('n') if key_with_modifier.has_no_modifiers() => { + Some(AdjustedInput::PermissionRequestResult( + permissions, + PermissionStatus::Denied, + )) + }, + _ => None, + } + } else { + match raw_input_bytes.as_slice() { + // Y or y + &[89] | &[121] => Some(AdjustedInput::PermissionRequestResult( + permissions, + PermissionStatus::Granted, + )), + // N or n + &[78] | &[110] => Some(AdjustedInput::PermissionRequestResult( + permissions, + PermissionStatus::Denied, + )), + _ => None, + } } + } else if let Some(key_with_modifier) = key_with_modifier { + Some(AdjustedInput::WriteKeyToPlugin(key_with_modifier.clone())) + } else if raw_input_bytes.as_slice() == BRACKETED_PASTE_BEGIN + || raw_input_bytes.as_slice() == BRACKETED_PASTE_END + { + // plugins do not need bracketed paste + None } else { - Some(AdjustedInput::WriteBytesToTerminal(input_bytes)) + Some(AdjustedInput::WriteBytesToTerminal(raw_input_bytes)) } } fn position_and_size(&self) -> PaneGeom { diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index ab36664f..56c7735e 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -16,7 +16,10 @@ use std::time::{self, Instant}; use zellij_utils::input::command::RunCommand; use zellij_utils::pane_size::Offset; use zellij_utils::{ - data::{InputMode, Palette, PaletteColor, PaneId as ZellijUtilsPaneId, Style}, + data::{ + BareKey, InputMode, KeyWithModifier, Palette, PaletteColor, PaneId as ZellijUtilsPaneId, + Style, + }, errors::prelude::*, input::layout::Run, pane_size::PaneGeom, @@ -37,8 +40,8 @@ const UP_ARROW: &[u8] = &[27, 91, 65]; const DOWN_ARROW: &[u8] = &[27, 91, 66]; const HOME_KEY: &[u8] = &[27, 91, 72]; const END_KEY: &[u8] = &[27, 91, 70]; -const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126]; -const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126]; +pub const BRACKETED_PASTE_BEGIN: &[u8] = &[27, 91, 50, 48, 48, 126]; +pub const BRACKETED_PASTE_END: &[u8] = &[27, 91, 50, 48, 49, 126]; const ENTER_NEWLINE: &[u8] = &[10]; const ESC: &[u8] = &[27]; const ENTER_CARRIAGE_RETURN: &[u8] = &[13]; @@ -190,92 +193,71 @@ impl Pane for TerminalPane { .cursor_coordinates() .map(|(x, y)| (x + left, y + top)) } - fn adjust_input_to_terminal(&mut self, input_bytes: Vec) -> Option { + fn adjust_input_to_terminal( + &mut self, + key_with_modifier: &Option, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, + ) -> Option { // there are some cases in which the terminal state means that input sent to it // needs to be adjusted. // here we match against those cases - if need be, we adjust the input and if not // 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 => { - return Some(AdjustedInput::WriteBytesToTerminal( - AnsiEncoding::Home.as_vec_bytes(), - )); - }, - END_KEY => { - return Some(AdjustedInput::WriteBytesToTerminal( - AnsiEncoding::End.as_vec_bytes(), - )); - }, - _ => {}, - }; + if !self.grid.bracketed_paste_mode { + // Zellij itself operates in bracketed paste mode, so the terminal sends these + // 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 + match raw_input_bytes.as_slice() { + BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => { + return Some(AdjustedInput::WriteBytesToTerminal(vec![])) + }, + _ => {}, } + } - if !self.grid.bracketed_paste_mode { - // Zellij itself operates in bracketed paste mode, so the terminal sends these - // 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 - match input_bytes.as_slice() { - BRACKETED_PASTE_BEGIN | BRACKETED_PASTE_END => { - return Some(AdjustedInput::WriteBytesToTerminal(vec![])) - }, - _ => {}, + if self.is_held.is_some() { + if key_with_modifier + .as_ref() + .map(|k| k.is_key_without_modifier(BareKey::Enter)) + .unwrap_or(false) + { + self.handle_held_run() + } 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 { @@ -804,6 +786,7 @@ impl TerminalPane { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_keyboard_protocol: bool, ) -> TerminalPane { let initial_pane_title = initial_pane_title.unwrap_or_else(|| format!("Pane #{}", pane_index)); @@ -819,6 +802,7 @@ impl TerminalPane { debug, arrow_fonts, styled_underlines, + explicitly_disable_keyboard_protocol, ); TerminalPane { frame: HashMap::new(), @@ -910,6 +894,131 @@ impl TerminalPane { self.banner = None; } } + fn adjust_input_to_terminal_with_kitty_keyboard_protocol( + &self, + key: &Option, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, + ) -> Option { + 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, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, + ) -> Option { + 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 { + 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 { + 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)] diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index 2459796b..4b8b10c2 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -32,6 +32,7 @@ fn vttest1_0() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -44,6 +45,7 @@ fn vttest1_0() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-0"; let content = read_fixture(fixture_name); @@ -61,6 +63,7 @@ fn vttest1_1() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -73,6 +76,7 @@ fn vttest1_1() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-1"; let content = read_fixture(fixture_name); @@ -90,6 +94,7 @@ fn vttest1_2() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -102,6 +107,7 @@ fn vttest1_2() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-2"; let content = read_fixture(fixture_name); @@ -119,6 +125,7 @@ fn vttest1_3() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -131,6 +138,7 @@ fn vttest1_3() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-3"; let content = read_fixture(fixture_name); @@ -148,6 +156,7 @@ fn vttest1_4() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -160,6 +169,7 @@ fn vttest1_4() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-4"; let content = read_fixture(fixture_name); @@ -177,6 +187,7 @@ fn vttest1_5() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -189,6 +200,7 @@ fn vttest1_5() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest1-5"; let content = read_fixture(fixture_name); @@ -206,6 +218,7 @@ fn vttest2_0() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -218,6 +231,7 @@ fn vttest2_0() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-0"; let content = read_fixture(fixture_name); @@ -235,6 +249,7 @@ fn vttest2_1() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -247,6 +262,7 @@ fn vttest2_1() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-1"; let content = read_fixture(fixture_name); @@ -264,6 +280,7 @@ fn vttest2_2() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -276,6 +293,7 @@ fn vttest2_2() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-2"; let content = read_fixture(fixture_name); @@ -293,6 +311,7 @@ fn vttest2_3() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -305,6 +324,7 @@ fn vttest2_3() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-3"; let content = read_fixture(fixture_name); @@ -322,6 +342,7 @@ fn vttest2_4() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -334,6 +355,7 @@ fn vttest2_4() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-4"; let content = read_fixture(fixture_name); @@ -351,6 +373,7 @@ fn vttest2_5() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -363,6 +386,7 @@ fn vttest2_5() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-5"; let content = read_fixture(fixture_name); @@ -380,6 +404,7 @@ fn vttest2_6() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -392,6 +417,7 @@ fn vttest2_6() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-6"; let content = read_fixture(fixture_name); @@ -409,6 +435,7 @@ fn vttest2_7() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -421,6 +448,7 @@ fn vttest2_7() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-7"; let content = read_fixture(fixture_name); @@ -438,6 +466,7 @@ fn vttest2_8() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -450,6 +479,7 @@ fn vttest2_8() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-8"; let content = read_fixture(fixture_name); @@ -467,6 +497,7 @@ fn vttest2_9() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -479,6 +510,7 @@ fn vttest2_9() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-9"; let content = read_fixture(fixture_name); @@ -496,6 +528,7 @@ fn vttest2_10() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -508,6 +541,7 @@ fn vttest2_10() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-10"; let content = read_fixture(fixture_name); @@ -525,6 +559,7 @@ fn vttest2_11() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -537,6 +572,7 @@ fn vttest2_11() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-11"; let content = read_fixture(fixture_name); @@ -554,6 +590,7 @@ fn vttest2_12() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -566,6 +603,7 @@ fn vttest2_12() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-12"; let content = read_fixture(fixture_name); @@ -583,6 +621,7 @@ fn vttest2_13() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -595,6 +634,7 @@ fn vttest2_13() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-13"; let content = read_fixture(fixture_name); @@ -612,6 +652,7 @@ fn vttest2_14() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -624,6 +665,7 @@ fn vttest2_14() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest2-14"; let content = read_fixture(fixture_name); @@ -641,6 +683,7 @@ fn vttest3_0() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -653,6 +696,7 @@ fn vttest3_0() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest3-0"; let content = read_fixture(fixture_name); @@ -670,6 +714,7 @@ fn vttest8_0() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -682,6 +727,7 @@ fn vttest8_0() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-0"; let content = read_fixture(fixture_name); @@ -699,6 +745,7 @@ fn vttest8_1() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -711,6 +758,7 @@ fn vttest8_1() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-1"; let content = read_fixture(fixture_name); @@ -728,6 +776,7 @@ fn vttest8_2() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -740,6 +789,7 @@ fn vttest8_2() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-2"; let content = read_fixture(fixture_name); @@ -757,6 +807,7 @@ fn vttest8_3() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -769,6 +820,7 @@ fn vttest8_3() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-3"; let content = read_fixture(fixture_name); @@ -786,6 +838,7 @@ fn vttest8_4() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -798,6 +851,7 @@ fn vttest8_4() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-4"; let content = read_fixture(fixture_name); @@ -815,6 +869,7 @@ fn vttest8_5() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -827,6 +882,7 @@ fn vttest8_5() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vttest8-5"; let content = read_fixture(fixture_name); @@ -844,6 +900,7 @@ fn csi_b() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -856,6 +913,7 @@ fn csi_b() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "csi-b"; let content = read_fixture(fixture_name); @@ -873,6 +931,7 @@ fn csi_capital_i() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -885,6 +944,7 @@ fn csi_capital_i() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "csi-capital-i"; let content = read_fixture(fixture_name); @@ -902,6 +962,7 @@ fn csi_capital_z() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -914,6 +975,7 @@ fn csi_capital_z() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "csi-capital-z"; let content = read_fixture(fixture_name); @@ -931,6 +993,7 @@ fn terminal_reports() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -943,6 +1006,7 @@ fn terminal_reports() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "terminal_reports"; let content = read_fixture(fixture_name); @@ -960,6 +1024,7 @@ fn wide_characters() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -972,6 +1037,7 @@ fn wide_characters() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters"; let content = read_fixture(fixture_name); @@ -989,6 +1055,7 @@ fn wide_characters_line_wrap() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1001,6 +1068,7 @@ fn wide_characters_line_wrap() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_line_wrap"; let content = read_fixture(fixture_name); @@ -1018,6 +1086,7 @@ fn insert_character_in_line_with_wide_character() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1030,6 +1099,7 @@ fn insert_character_in_line_with_wide_character() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_middle_line_insert"; let content = read_fixture(fixture_name); @@ -1047,6 +1117,7 @@ fn delete_char_in_middle_of_line_with_widechar() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1059,6 +1130,7 @@ fn delete_char_in_middle_of_line_with_widechar() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide-chars-delete-middle"; let content = read_fixture(fixture_name); @@ -1076,6 +1148,7 @@ fn delete_char_in_middle_of_line_with_multiple_widechars() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1088,6 +1161,7 @@ fn delete_char_in_middle_of_line_with_multiple_widechars() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide-chars-delete-middle-after-multi"; let content = read_fixture(fixture_name); @@ -1105,6 +1179,7 @@ fn fish_wide_characters_override_clock() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1117,6 +1192,7 @@ fn fish_wide_characters_override_clock() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fish_wide_characters_override_clock"; let content = read_fixture(fixture_name); @@ -1134,6 +1210,7 @@ fn bash_delete_wide_characters() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1146,6 +1223,7 @@ fn bash_delete_wide_characters() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "bash_delete_wide_characters"; let content = read_fixture(fixture_name); @@ -1163,6 +1241,7 @@ fn delete_wide_characters_before_cursor() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1175,6 +1254,7 @@ fn delete_wide_characters_before_cursor() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "delete_wide_characters_before_cursor"; let content = read_fixture(fixture_name); @@ -1192,6 +1272,7 @@ fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1204,6 +1285,7 @@ fn delete_wide_characters_before_cursor_when_cursor_is_on_wide_character() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "delete_wide_characters_before_cursor_when_cursor_is_on_wide_character"; let content = read_fixture(fixture_name); @@ -1221,6 +1303,7 @@ fn delete_wide_character_under_cursor() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1233,6 +1316,7 @@ fn delete_wide_character_under_cursor() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "delete_wide_character_under_cursor"; let content = read_fixture(fixture_name); @@ -1250,6 +1334,7 @@ fn replace_wide_character_under_cursor() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 104, @@ -1262,6 +1347,7 @@ fn replace_wide_character_under_cursor() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "replace_wide_character_under_cursor"; let content = read_fixture(fixture_name); @@ -1279,6 +1365,7 @@ fn wrap_wide_characters() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 90, @@ -1291,6 +1378,7 @@ fn wrap_wide_characters() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -1308,6 +1396,7 @@ fn wrap_wide_characters_on_size_change() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 93, @@ -1320,6 +1409,7 @@ fn wrap_wide_characters_on_size_change() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -1338,6 +1428,7 @@ fn unwrap_wide_characters_on_size_change() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 93, @@ -1350,6 +1441,7 @@ fn unwrap_wide_characters_on_size_change() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_full"; let content = read_fixture(fixture_name); @@ -1369,6 +1461,7 @@ fn wrap_wide_characters_in_the_middle_of_the_line() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 91, @@ -1381,6 +1474,7 @@ fn wrap_wide_characters_in_the_middle_of_the_line() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_line_middle"; let content = read_fixture(fixture_name); @@ -1398,6 +1492,7 @@ fn wrap_wide_characters_at_the_end_of_the_line() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 90, @@ -1410,6 +1505,7 @@ fn wrap_wide_characters_at_the_end_of_the_line() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "wide_characters_line_end"; let content = read_fixture(fixture_name); @@ -1427,6 +1523,7 @@ fn copy_selected_text_from_viewport() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 27, 125, @@ -1439,6 +1536,7 @@ fn copy_selected_text_from_viewport() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -1464,6 +1562,7 @@ fn copy_wrapped_selected_text_from_viewport() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 22, 73, @@ -1476,6 +1575,7 @@ fn copy_wrapped_selected_text_from_viewport() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "grid_copy_wrapped"; let content = read_fixture(fixture_name); @@ -1500,6 +1600,7 @@ fn copy_selected_text_from_lines_above() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 27, 125, @@ -1512,6 +1613,7 @@ fn copy_selected_text_from_lines_above() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -1537,6 +1639,7 @@ fn copy_selected_text_from_lines_below() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 27, 125, @@ -1549,6 +1652,7 @@ fn copy_selected_text_from_lines_below() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "grid_copy"; let content = read_fixture(fixture_name); @@ -1582,6 +1686,7 @@ fn run_bandwhich_from_fish_shell() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1594,6 +1699,7 @@ fn run_bandwhich_from_fish_shell() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fish_and_bandwhich"; let content = read_fixture(fixture_name); @@ -1611,6 +1717,7 @@ fn fish_tab_completion_options() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1623,6 +1730,7 @@ fn fish_tab_completion_options() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fish_tab_completion_options"; let content = read_fixture(fixture_name); @@ -1646,6 +1754,7 @@ pub fn fish_select_tab_completion_options() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1658,6 +1767,7 @@ pub fn fish_select_tab_completion_options() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fish_select_tab_completion_options"; let content = read_fixture(fixture_name); @@ -1685,6 +1795,7 @@ pub fn vim_scroll_region_down() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1697,6 +1808,7 @@ pub fn vim_scroll_region_down() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vim_scroll_region_down"; let content = read_fixture(fixture_name); @@ -1721,6 +1833,7 @@ pub fn vim_ctrl_d() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1733,6 +1846,7 @@ pub fn vim_ctrl_d() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vim_ctrl_d"; let content = read_fixture(fixture_name); @@ -1756,6 +1870,7 @@ pub fn vim_ctrl_u() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1768,6 +1883,7 @@ pub fn vim_ctrl_u() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vim_ctrl_u"; let content = read_fixture(fixture_name); @@ -1785,6 +1901,7 @@ pub fn htop() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1797,6 +1914,7 @@ pub fn htop() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "htop"; let content = read_fixture(fixture_name); @@ -1814,6 +1932,7 @@ pub fn htop_scrolling() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1826,6 +1945,7 @@ pub fn htop_scrolling() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "htop_scrolling"; let content = read_fixture(fixture_name); @@ -1843,6 +1963,7 @@ pub fn htop_right_scrolling() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1855,6 +1976,7 @@ pub fn htop_right_scrolling() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "htop_right_scrolling"; let content = read_fixture(fixture_name); @@ -1882,6 +2004,7 @@ pub fn vim_overwrite() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1894,6 +2017,7 @@ pub fn vim_overwrite() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "vim_overwrite"; let content = read_fixture(fixture_name); @@ -1913,6 +2037,7 @@ pub fn clear_scroll_region() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1925,6 +2050,7 @@ pub fn clear_scroll_region() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "clear_scroll_region"; let content = read_fixture(fixture_name); @@ -1942,6 +2068,7 @@ pub fn display_tab_characters_properly() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1954,6 +2081,7 @@ pub fn display_tab_characters_properly() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "tab_characters"; let content = read_fixture(fixture_name); @@ -1971,6 +2099,7 @@ pub fn neovim_insert_mode() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -1983,6 +2112,7 @@ pub fn neovim_insert_mode() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "nvim_insert"; let content = read_fixture(fixture_name); @@ -2000,6 +2130,7 @@ pub fn bash_cursor_linewrap() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 116, @@ -2012,6 +2143,7 @@ pub fn bash_cursor_linewrap() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "bash_cursor_linewrap"; let content = read_fixture(fixture_name); @@ -2031,6 +2163,7 @@ pub fn fish_paste_multiline() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 149, @@ -2043,6 +2176,7 @@ pub fn fish_paste_multiline() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fish_paste_multiline"; let content = read_fixture(fixture_name); @@ -2060,6 +2194,7 @@ pub fn git_log() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 149, @@ -2072,6 +2207,7 @@ pub fn git_log() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "git_log"; let content = read_fixture(fixture_name); @@ -2091,6 +2227,7 @@ pub fn git_diff_scrollup() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 28, 149, @@ -2103,6 +2240,7 @@ pub fn git_diff_scrollup() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "git_diff_scrollup"; let content = read_fixture(fixture_name); @@ -2120,6 +2258,7 @@ pub fn emacs_longbuf() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 60, 284, @@ -2132,6 +2271,7 @@ pub fn emacs_longbuf() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "emacs_longbuf_tutorial"; let content = read_fixture(fixture_name); @@ -2149,6 +2289,7 @@ pub fn top_and_quit() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 56, 235, @@ -2161,6 +2302,7 @@ pub fn top_and_quit() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "top_and_quit"; let content = read_fixture(fixture_name); @@ -2185,6 +2327,7 @@ pub fn exa_plus_omf_theme() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 56, 235, @@ -2197,6 +2340,7 @@ pub fn exa_plus_omf_theme() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "exa_plus_omf_theme"; let content = read_fixture(fixture_name); @@ -2214,6 +2358,7 @@ pub fn scroll_up() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 50, @@ -2226,6 +2371,7 @@ pub fn scroll_up() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2244,6 +2390,7 @@ pub fn scroll_down() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 50, @@ -2256,6 +2403,7 @@ pub fn scroll_down() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2275,6 +2423,7 @@ pub fn scroll_up_with_line_wraps() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 25, @@ -2287,6 +2436,7 @@ pub fn scroll_up_with_line_wraps() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2305,6 +2455,7 @@ pub fn scroll_down_with_line_wraps() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 25, @@ -2317,6 +2468,7 @@ pub fn scroll_down_with_line_wraps() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2336,6 +2488,7 @@ pub fn scroll_up_decrease_width_and_scroll_down() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 50, @@ -2348,6 +2501,7 @@ pub fn scroll_up_decrease_width_and_scroll_down() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2372,6 +2526,7 @@ pub fn scroll_up_increase_width_and_scroll_down() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 10, 25, @@ -2384,6 +2539,7 @@ pub fn scroll_up_increase_width_and_scroll_down() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scrolling"; let content = read_fixture(fixture_name); @@ -2408,6 +2564,7 @@ fn saved_cursor_across_resize() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 4, 20, @@ -2420,6 +2577,7 @@ fn saved_cursor_across_resize() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut parse = |s, grid: &mut Grid| { for b in Vec::from(s) { @@ -2453,6 +2611,7 @@ fn saved_cursor_across_resize_longline() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 4, 20, @@ -2465,6 +2624,7 @@ fn saved_cursor_across_resize_longline() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut parse = |s, grid: &mut Grid| { for b in Vec::from(s) { @@ -2491,6 +2651,7 @@ fn saved_cursor_across_resize_rewrap() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 4, 4 * 8, @@ -2503,6 +2664,7 @@ fn saved_cursor_across_resize_rewrap() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut parse = |s, grid: &mut Grid| { for b in Vec::from(s) { @@ -2529,6 +2691,7 @@ pub fn move_cursor_below_scroll_region() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 34, 114, @@ -2541,6 +2704,7 @@ pub fn move_cursor_below_scroll_region() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "move_cursor_below_scroll_region"; let content = read_fixture(fixture_name); @@ -2558,6 +2722,7 @@ pub fn insert_wide_characters_in_existing_line() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 21, 86, @@ -2570,6 +2735,7 @@ pub fn insert_wide_characters_in_existing_line() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "chinese_characters_line_middle"; let content = read_fixture(fixture_name); @@ -2593,6 +2759,7 @@ pub fn full_screen_scroll_region_and_scroll_up() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 54, 80, @@ -2605,6 +2772,7 @@ pub fn full_screen_scroll_region_and_scroll_up() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scroll_region_full_screen"; let content = read_fixture(fixture_name); @@ -2625,6 +2793,7 @@ pub fn ring_bell() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 134, 64, @@ -2637,6 +2806,7 @@ pub fn ring_bell() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "ring_bell"; let content = read_fixture(fixture_name); @@ -2654,6 +2824,7 @@ pub fn alternate_screen_change_size() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 20, 20, @@ -2666,6 +2837,7 @@ pub fn alternate_screen_change_size() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "alternate_screen_change_size"; let content = read_fixture(fixture_name); @@ -2687,6 +2859,7 @@ pub fn fzf_fullscreen() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2699,6 +2872,7 @@ pub fn fzf_fullscreen() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "fzf_fullscreen"; let content = read_fixture(fixture_name); @@ -2720,6 +2894,7 @@ pub fn replace_multiple_wide_characters_under_cursor() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2732,6 +2907,7 @@ pub fn replace_multiple_wide_characters_under_cursor() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "replace_multiple_wide_characters"; let content = read_fixture(fixture_name); @@ -2753,6 +2929,7 @@ pub fn replace_non_wide_characters_with_wide_characters() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2765,6 +2942,7 @@ pub fn replace_non_wide_characters_with_wide_characters() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "replace_non_wide_characters_with_wide_characters"; let content = read_fixture(fixture_name); @@ -2782,6 +2960,7 @@ pub fn scroll_down_ansi() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2794,6 +2973,7 @@ pub fn scroll_down_ansi() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "scroll_down"; let content = read_fixture(fixture_name); @@ -2811,6 +2991,7 @@ pub fn ansi_capital_t() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2823,6 +3004,7 @@ pub fn ansi_capital_t() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "foo\u{1b}[14Tbar".as_bytes(); for byte in content { @@ -2839,6 +3021,7 @@ pub fn ansi_capital_s() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2851,6 +3034,7 @@ pub fn ansi_capital_s() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nfoo\u{1b}[14Sbar".as_bytes(); for byte in content { @@ -2867,6 +3051,7 @@ fn terminal_pixel_size_reports() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -2882,6 +3067,7 @@ fn terminal_pixel_size_reports() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "terminal_pixel_size_reports"; let content = read_fixture(fixture_name); @@ -2905,6 +3091,7 @@ fn terminal_pixel_size_reports_in_unsupported_terminals() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -2917,6 +3104,7 @@ fn terminal_pixel_size_reports_in_unsupported_terminals() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "terminal_pixel_size_reports"; let content = read_fixture(fixture_name); @@ -2941,6 +3129,7 @@ pub fn ansi_csi_at_sign() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2953,6 +3142,7 @@ pub fn ansi_csi_at_sign() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "foo\u{1b}[2D\u{1b}[2@".as_bytes(); for byte in content { @@ -2973,6 +3163,7 @@ pub fn sixel_images_are_reaped_when_scrolled_off() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -2985,6 +3176,7 @@ pub fn sixel_images_are_reaped_when_scrolled_off() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let pane_content = read_fixture("sixel-image-500px.six"); for byte in pane_content { @@ -3014,6 +3206,7 @@ pub fn sixel_images_are_reaped_when_resetting() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 112, @@ -3026,6 +3219,7 @@ pub fn sixel_images_are_reaped_when_resetting() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let pane_content = read_fixture("sixel-image-500px.six"); for byte in pane_content { @@ -3052,6 +3246,7 @@ pub fn sixel_image_in_alternate_buffer() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 30, 112, @@ -3064,6 +3259,7 @@ pub fn sixel_image_in_alternate_buffer() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let move_to_alternate_screen = "\u{1b}[?1049h"; @@ -3101,6 +3297,7 @@ pub fn sixel_with_image_scrolling_decsdm() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 30, 112, @@ -3113,6 +3310,7 @@ pub fn sixel_with_image_scrolling_decsdm() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // enter DECSDM @@ -3169,6 +3367,7 @@ pub fn osc_4_background_query() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -3181,6 +3380,7 @@ pub fn osc_4_background_query() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\u{1b}]10;?\u{1b}\\"; for byte in content.as_bytes() { @@ -3205,6 +3405,7 @@ pub fn osc_4_foreground_query() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -3217,6 +3418,7 @@ pub fn osc_4_foreground_query() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\u{1b}]11;?\u{1b}\\"; for byte in content.as_bytes() { @@ -3243,6 +3445,7 @@ pub fn osc_4_color_query() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -3255,6 +3458,7 @@ pub fn osc_4_color_query() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\u{1b}]4;222;?\u{1b}\\"; for byte in content.as_bytes() { @@ -3279,6 +3483,7 @@ pub fn xtsmgraphics_color_register_count() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -3291,6 +3496,7 @@ pub fn xtsmgraphics_color_register_count() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\u{1b}[?1;1;S\u{1b}\\"; for byte in content.as_bytes() { @@ -3319,6 +3525,7 @@ pub fn xtsmgraphics_pixel_graphics_geometry() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 51, 97, @@ -3331,6 +3538,7 @@ pub fn xtsmgraphics_pixel_graphics_geometry() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let content = "\u{1b}[?2;1;S\u{1b}\\"; for byte in content.as_bytes() { @@ -3359,6 +3567,7 @@ pub fn cursor_hide_persists_through_alternate_screen() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 30, 112, @@ -3371,6 +3580,7 @@ pub fn cursor_hide_persists_through_alternate_screen() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let hide_cursor = "\u{1b}[?25l"; @@ -3413,6 +3623,7 @@ fn table_ui_component() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -3425,6 +3636,7 @@ fn table_ui_component() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "table-ui-component"; let content = read_fixture(fixture_name); @@ -3442,6 +3654,7 @@ fn table_ui_component_with_coordinates() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -3454,6 +3667,7 @@ fn table_ui_component_with_coordinates() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "table-ui-component-with-coordinates"; let content = read_fixture(fixture_name); @@ -3471,6 +3685,7 @@ fn ribbon_ui_component() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -3483,6 +3698,7 @@ fn ribbon_ui_component() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "ribbon-ui-component"; let content = read_fixture(fixture_name); @@ -3500,6 +3716,7 @@ fn ribbon_ui_component_with_coordinates() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 110, @@ -3512,6 +3729,7 @@ fn ribbon_ui_component_with_coordinates() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "ribbon-ui-component-with-coordinates"; let content = read_fixture(fixture_name); @@ -3529,6 +3747,7 @@ fn nested_list_ui_component() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 120, @@ -3541,6 +3760,7 @@ fn nested_list_ui_component() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "nested-list-ui-component"; let content = read_fixture(fixture_name); @@ -3558,6 +3778,7 @@ fn nested_list_ui_component_with_coordinates() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 120, @@ -3570,6 +3791,7 @@ fn nested_list_ui_component_with_coordinates() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "nested-list-ui-component-with-coordinates"; let content = read_fixture(fixture_name); @@ -3587,6 +3809,7 @@ fn text_ui_component() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 120, @@ -3599,6 +3822,7 @@ fn text_ui_component() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "text-ui-component"; let content = read_fixture(fixture_name); @@ -3616,6 +3840,7 @@ fn text_ui_component_with_coordinates() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( 41, 120, @@ -3628,6 +3853,7 @@ fn text_ui_component_with_coordinates() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let fixture_name = "text-ui-component-with-coordinates"; let content = read_fixture(fixture_name); diff --git a/zellij-server/src/panes/unit/search_in_pane_tests.rs b/zellij-server/src/panes/unit/search_in_pane_tests.rs index 5f0d7a58..3ab06501 100644 --- a/zellij-server/src/panes/unit/search_in_pane_tests.rs +++ b/zellij-server/src/panes/unit/search_in_pane_tests.rs @@ -31,6 +31,7 @@ fn create_pane() -> TerminalPane { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -47,6 +48,7 @@ fn create_pane() -> TerminalPane { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let content = read_fixture(); terminal_pane.handle_pty_bytes(content); diff --git a/zellij-server/src/panes/unit/terminal_pane_tests.rs b/zellij-server/src/panes/unit/terminal_pane_tests.rs index ddc19a40..b568d164 100644 --- a/zellij-server/src/panes/unit/terminal_pane_tests.rs +++ b/zellij-server/src/panes/unit/terminal_pane_tests.rs @@ -39,6 +39,7 @@ pub fn scrolling_inside_a_pane() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -55,6 +56,7 @@ pub fn scrolling_inside_a_pane() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..30 { @@ -87,6 +89,7 @@ pub fn sixel_image_inside_terminal_pane() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -103,6 +106,7 @@ pub fn sixel_image_inside_terminal_pane() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let sixel_image_bytes = "\u{1b}Pq #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 arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -151,6 +156,7 @@ pub fn partial_sixel_image_inside_terminal_pane() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let pane_content = read_fixture("sixel-image-500px.six"); terminal_pane.handle_pty_bytes(pane_content); @@ -177,6 +183,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -193,6 +200,7 @@ pub fn overflowing_sixel_image_inside_terminal_pane() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let pane_content = read_fixture("sixel-image-500px.six"); terminal_pane.handle_pty_bytes(pane_content); @@ -218,6 +226,7 @@ pub fn scrolling_through_a_sixel_image() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -234,6 +243,7 @@ pub fn scrolling_through_a_sixel_image() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..30 { @@ -270,6 +280,7 @@ pub fn multiple_sixel_images_in_pane() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -286,6 +297,7 @@ pub fn multiple_sixel_images_in_pane() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -320,6 +332,7 @@ pub fn resizing_pane_with_sixel_images() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -336,6 +349,7 @@ pub fn resizing_pane_with_sixel_images() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -373,6 +387,7 @@ pub fn changing_character_cell_size_with_sixel_images() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -389,6 +404,7 @@ pub fn changing_character_cell_size_with_sixel_images() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index let mut text_to_fill_pane = String::new(); for i in 0..5 { @@ -431,6 +447,7 @@ pub fn keep_working_after_corrupted_sixel_image() { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -447,6 +464,7 @@ pub fn keep_working_after_corrupted_sixel_image() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index 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 arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -503,6 +522,7 @@ pub fn pane_with_frame_position_is_on_frame() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index 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 arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -595,6 +616,7 @@ pub fn pane_with_bottom_and_right_borders_position_is_on_frame() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index 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 arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut terminal_pane = TerminalPane::new( pid, fake_win_size, @@ -687,6 +710,7 @@ pub fn frameless_pane_position_is_on_frame() { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); // 0 is the pane index terminal_pane.set_content_offset(Offset::default()); diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs index 1f502fff..22a1bf1f 100644 --- a/zellij-server/src/plugins/unit/plugin_tests.rs +++ b/zellij-server/src/plugins/unit/plugin_tests.rs @@ -6,7 +6,9 @@ use std::collections::BTreeMap; use std::path::PathBuf; use tempfile::tempdir; 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::input::layout::{ 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![( None, Some(client_id), - Event::Key(Key::Char('a')), // this triggers a SwitchToMode(Tab) command in the fixture - // plugin + Event::Key(KeyWithModifier::new(BareKey::Char('a'))), // this triggers a SwitchToMode(Tab) command in the fixture + // plugin )])); screen_thread.join().unwrap(); // this might take a while if the cache is cold teardown(); @@ -1088,8 +1090,8 @@ pub fn switch_to_mode_plugin_command_permission_denied() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, Some(client_id), - Event::Key(Key::Char('a')), // this triggers a SwitchToMode(Tab) command in the fixture - // plugin + Event::Key(KeyWithModifier::new(BareKey::Char('a'))), // this triggers a SwitchToMode(Tab) command in the fixture + // plugin )])); screen_thread.join().unwrap(); // this might take a while if the cache is cold teardown(); @@ -1160,8 +1162,8 @@ pub fn new_tabs_with_layout_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, Some(client_id), - Event::Key(Key::Char('b')), // this triggers a new_tabs_with_layout command in the fixture - // plugin + Event::Key(KeyWithModifier::new(BareKey::Char('b'))), // this triggers a new_tabs_with_layout command in the fixture + // plugin )])); screen_thread.join().unwrap(); // this might take a while if the cache is cold teardown(); @@ -1246,8 +1248,8 @@ pub fn new_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, Some(client_id), - Event::Key(Key::Char('c')), // this triggers a new_tab command in the fixture - // plugin + Event::Key(KeyWithModifier::new(BareKey::Char('c'))), // this triggers a new_tab command in the fixture + // plugin )])); screen_thread.join().unwrap(); // this might take a while if the cache is cold teardown(); @@ -1318,7 +1320,7 @@ pub fn go_to_next_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1389,7 +1391,7 @@ pub fn go_to_previous_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1460,7 +1462,7 @@ pub fn resize_focused_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1531,7 +1533,7 @@ pub fn resize_focused_pane_with_direction_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1602,7 +1604,7 @@ pub fn focus_next_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1673,7 +1675,7 @@ pub fn focus_previous_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1744,7 +1746,7 @@ pub fn move_focus_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1815,7 +1817,7 @@ pub fn move_focus_or_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1886,7 +1888,7 @@ pub fn edit_scrollback_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -1957,7 +1959,7 @@ pub fn write_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2028,7 +2030,7 @@ pub fn write_chars_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2099,7 +2101,7 @@ pub fn toggle_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2170,7 +2172,7 @@ pub fn move_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2241,7 +2243,7 @@ pub fn move_pane_with_direction_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2313,7 +2315,7 @@ pub fn clear_screen_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2385,7 +2387,7 @@ pub fn scroll_up_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2456,7 +2458,7 @@ pub fn scroll_down_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2527,7 +2529,7 @@ pub fn scroll_to_top_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2598,7 +2600,7 @@ pub fn scroll_to_bottom_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2669,7 +2671,7 @@ pub fn page_scroll_up_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2740,7 +2742,7 @@ pub fn page_scroll_down_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2811,7 +2813,7 @@ pub fn toggle_focus_fullscreen_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2882,7 +2884,7 @@ pub fn toggle_pane_frames_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -2953,7 +2955,7 @@ pub fn toggle_pane_embed_or_eject_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3024,7 +3026,7 @@ pub fn undo_rename_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3095,7 +3097,7 @@ pub fn close_focus_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3166,7 +3168,7 @@ pub fn toggle_active_tab_sync_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3237,7 +3239,7 @@ pub fn close_focused_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3308,7 +3310,7 @@ pub fn undo_rename_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3379,7 +3381,7 @@ pub fn previous_swap_layout_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3450,7 +3452,7 @@ pub fn next_swap_layout_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3521,7 +3523,7 @@ pub fn go_to_tab_name_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3592,7 +3594,7 @@ pub fn focus_or_create_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3663,7 +3665,7 @@ pub fn go_to_tab() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3734,7 +3736,7 @@ pub fn start_or_reload_plugin() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3812,7 +3814,7 @@ pub fn quit_zellij_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3890,7 +3892,7 @@ pub fn detach_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -3968,7 +3970,7 @@ pub fn open_file_floating_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4050,7 +4052,7 @@ pub fn open_file_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4133,7 +4135,7 @@ pub fn open_file_with_line_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4215,7 +4217,7 @@ pub fn open_file_with_line_floating_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4297,7 +4299,7 @@ pub fn open_terminal_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4375,7 +4377,7 @@ pub fn open_terminal_floating_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4453,7 +4455,7 @@ pub fn open_command_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4531,7 +4533,7 @@ pub fn open_command_pane_floating_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4602,7 +4604,7 @@ pub fn switch_to_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4673,7 +4675,7 @@ pub fn hide_self_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4743,7 +4745,7 @@ pub fn show_self_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4814,7 +4816,7 @@ pub fn close_terminal_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4885,7 +4887,7 @@ pub fn close_plugin_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -4956,7 +4958,7 @@ pub fn focus_terminal_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5027,7 +5029,7 @@ pub fn focus_plugin_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5098,7 +5100,7 @@ pub fn rename_terminal_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5169,7 +5171,7 @@ pub fn rename_plugin_pane_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5240,7 +5242,7 @@ pub fn rename_tab_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5320,7 +5322,7 @@ pub fn send_configuration_to_plugins() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5388,7 +5390,7 @@ pub fn request_plugin_permissions() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5480,7 +5482,7 @@ pub fn granted_permission_request_result() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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(); teardown(); @@ -5571,7 +5573,7 @@ pub fn denied_permission_request_result() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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(); teardown(); @@ -5642,7 +5644,7 @@ pub fn run_command_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5720,7 +5722,7 @@ pub fn run_command_with_env_vars_and_cwd_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -5798,7 +5800,7 @@ pub fn web_request_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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 teardown(); @@ -6219,7 +6221,7 @@ pub fn switch_session_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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)); teardown(); @@ -6300,7 +6302,7 @@ pub fn switch_session_with_layout_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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)); teardown(); @@ -6381,7 +6383,7 @@ pub fn switch_session_with_layout_and_cwd_plugin_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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)); teardown(); @@ -6462,7 +6464,7 @@ pub fn disconnect_other_clients_plugins_command() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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)); teardown(); @@ -6547,13 +6549,13 @@ pub fn run_plugin_in_specific_cwd() { let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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)); let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![( None, 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(); server_thread.join().unwrap(); // this might take a while if the cache is cold diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap index 8c881def..50edf7ad 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_chars_plugin_command.snap @@ -1,15 +1,17 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 1449 +assertion_line: 2047 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( WriteCharacter( + None, [ 102, 111, 111, ], + false, 1, ), ) diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_plugin_command.snap index 7f8f3622..1075fffe 100644 --- a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_plugin_command.snap +++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__write_plugin_command.snap @@ -1,15 +1,17 @@ --- source: zellij-server/src/plugins/./unit/plugin_tests.rs -assertion_line: 1117 +assertion_line: 1976 expression: "format!(\"{:#?}\", new_tab_event)" --- Some( WriteCharacter( + None, [ 102, 111, 111, ], + false, 1, ), ) diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs index ae40fe6f..a9ba489c 100644 --- a/zellij-server/src/plugins/wasm_bridge.rs +++ b/zellij-server/src/plugins/wasm_bridge.rs @@ -543,8 +543,6 @@ impl WasmBridge { mut updates: Vec<(Option, Option, Event)>, shutdown_sender: Sender<()>, ) -> Result<()> { - let err_context = || "failed to update plugin state".to_string(); - let plugins_to_update: Vec<( PluginId, ClientId, diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs index 1e169f84..8ec4064f 100644 --- a/zellij-server/src/plugins/zellij_exports.rs +++ b/zellij-server/src/plugins/zellij_exports.rs @@ -1051,7 +1051,7 @@ fn edit_scrollback(env: &ForeignFunctionEnv) { fn write(env: &ForeignFunctionEnv, bytes: Vec) { 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); } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index fc50fd2c..19f11ccb 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -64,12 +64,17 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::ToggleTab(client_id)) .with_context(err_context)?; }, - Action::Write(val) => { + Action::Write(key_with_modifier, raw_bytes, is_kitty_keyboard_protocol) => { senders .send_to_screen(ScreenInstruction::ClearScroll(client_id)) .with_context(err_context)?; 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)?; }, Action::WriteChars(val) => { @@ -78,7 +83,9 @@ pub(crate) fn route_action( .with_context(err_context)?; let val = val.into_bytes(); senders - .send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) + .send_to_screen(ScreenInstruction::WriteCharacter( + None, val, false, client_id, + )) .with_context(err_context)?; }, Action::SwitchToMode(mode) => { diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 75d34562..8cf16dd8 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -9,7 +9,7 @@ use std::time::Duration; use log::{debug, warn}; 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::input::command::RunCommand; @@ -158,7 +158,8 @@ pub enum ScreenInstruction { ToggleFloatingPanes(ClientId, Option), HorizontalSplit(PaneId, Option, HoldForCommand, ClientId), VerticalSplit(PaneId, Option, HoldForCommand, ClientId), - WriteCharacter(Vec, ClientId), + WriteCharacter(Option, Vec, bool, ClientId), // bool -> + // is_kitty_keyboard_protocol Resize(ClientId, ResizeStrategy), SwitchFocus(ClientId), FocusNextPane(ClientId), @@ -621,6 +622,7 @@ pub(crate) struct Screen { arrow_fonts: bool, layout_dir: Option, default_layout_name: Option, + explicitly_disable_kitty_keyboard_protocol: bool, } impl Screen { @@ -644,6 +646,7 @@ impl Screen { styled_underlines: bool, arrow_fonts: bool, layout_dir: Option, + explicitly_disable_kitty_keyboard_protocol: bool, ) -> Self { let session_name = mode_info.session_name.clone().unwrap_or_default(); let session_info = SessionInfo::new(session_name.clone()); @@ -684,6 +687,7 @@ impl Screen { arrow_fonts, resurrectable_sessions, layout_dir, + explicitly_disable_kitty_keyboard_protocol, } } @@ -1232,6 +1236,7 @@ impl Screen { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); self.tabs.insert(tab_index, tab); Ok(()) @@ -2317,6 +2322,13 @@ pub(crate) fn screen_thread_main( config_options.copy_on_select.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 mut screen = Screen::new( @@ -2345,6 +2357,7 @@ pub(crate) fn screen_thread_main( styled_underlines, arrow_fonts, layout_dir, + explicitly_disable_kitty_keyboard_protocol, ); let mut pending_tab_ids: HashSet = HashSet::new(); @@ -2536,15 +2549,20 @@ pub(crate) fn screen_thread_main( screen.log_and_report_session_state()?; 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; active_tab_and_connected_client_id!( screen, client_id, |tab: &mut Tab, client_id: ClientId| { let write_result = match tab.is_sync_panes_active() { - true => tab.write_to_terminals_on_current_tab(bytes, client_id), - false => tab.write_to_active_terminal(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(&key_with_modifier, raw_bytes, is_kitty_keyboard_protocol, client_id), }; if let Ok(true) = write_result { state_changed = true; diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index 93188be7..0a001af3 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -41,6 +41,7 @@ pub struct LayoutApplier<'a> { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_kitty_keyboard_protocol: bool, } impl<'a> LayoutApplier<'a> { @@ -63,6 +64,7 @@ impl<'a> LayoutApplier<'a> { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_kitty_keyboard_protocol: bool, ) -> Self { let viewport = viewport.clone(); let senders = senders.clone(); @@ -94,6 +96,7 @@ impl<'a> LayoutApplier<'a> { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, } } pub fn apply_layout( @@ -330,6 +333,7 @@ impl<'a> LayoutApplier<'a> { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); if let Some(pane_initial_contents) = &layout.pane_initial_contents { new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); @@ -448,6 +452,7 @@ impl<'a> LayoutApplier<'a> { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); if let Some(pane_initial_contents) = &floating_pane_layout.pane_initial_contents { new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 089ba2e1..a548cc9e 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -11,7 +11,8 @@ use std::env::temp_dir; use std::path::PathBuf; use uuid::Uuid; 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::input::command::RunCommand; @@ -187,6 +188,7 @@ pub(crate) struct Tab { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_kitty_keyboard_protocol: bool, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -215,7 +217,12 @@ pub trait Pane { fn handle_pty_bytes(&mut self, _bytes: VteBytes) {} fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {} fn cursor_coordinates(&self) -> Option<(usize, usize)>; - fn adjust_input_to_terminal(&mut self, _input_bytes: Vec) -> Option { + fn adjust_input_to_terminal( + &mut self, + _key_with_modifier: &Option, + _raw_input_bytes: Vec, + _raw_input_bytes_are_kitty: bool, + ) -> Option { None } fn position_and_size(&self) -> PaneGeom; @@ -487,6 +494,7 @@ pub enum AdjustedInput { PermissionRequestResult(Vec, PermissionStatus), CloseThisPane, DropToShellInThisPane { working_dir: Option }, + WriteKeyToPlugin(KeyWithModifier), } pub fn get_next_terminal_position( tiled_panes: &TiledPanes, @@ -537,6 +545,7 @@ impl Tab { debug: bool, arrow_fonts: bool, styled_underlines: bool, + explicitly_disable_kitty_keyboard_protocol: bool, ) -> Self { let name = if name.is_empty() { format!("Tab #{}", index + 1) @@ -627,6 +636,7 @@ impl Tab { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, } } @@ -660,6 +670,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ) .apply_layout( layout, @@ -723,6 +734,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ) .apply_floating_panes_layout_to_existing_panes( &layout_candidate, @@ -779,6 +791,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ) .apply_tiled_panes_layout_to_existing_panes( &layout_candidate, @@ -1084,6 +1097,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, )) as Box }, PaneId::Plugin(plugin_pid) => { @@ -1146,6 +1160,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); new_pane.update_name("EDITING SCROLLBACK"); // we do this here and not in the // constructor so it won't be overrided @@ -1220,6 +1235,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); let replaced_pane = if self.floating_panes.panes_contain(&old_pane_id) { self.floating_panes @@ -1344,6 +1360,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); self.tiled_panes .split_pane_horizontally(pid, Box::new(new_terminal), client_id); @@ -1403,6 +1420,7 @@ impl Tab { self.debug, self.arrow_fonts, self.styled_underlines, + self.explicitly_disable_kitty_keyboard_protocol, ); self.tiled_panes .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 clipboard_update = terminal_output.drain_clipboard_update(); 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)?; } if let Some(string) = clipboard_update { @@ -1604,7 +1622,9 @@ impl Tab { pub fn write_to_terminals_on_current_tab( &mut self, - input_bytes: Vec, + key_with_modifier: &Option, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, client_id: ClientId, ) -> Result { // 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(); for pane_id in pane_ids { 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")?; if ui_change_triggered { should_trigger_ui_change = true; @@ -1624,14 +1650,16 @@ impl Tab { pub fn write_to_active_terminal( &mut self, - input_bytes: Vec, + key_with_modifier: &Option, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, client_id: ClientId, ) -> Result { // returns true if a UI update should be triggered (eg. if a command pane // was closed with ctrl-c) let err_context = || { 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) .with_context(err_context)? }; - // Can't use 'err_context' here since it borrows 'input_bytes' - self.write_to_pane_id(input_bytes, pane_id, Some(client_id)) - .with_context(|| format!("failed to write to active terminal for client {client_id}")) + // Can't use 'err_context' here since it borrows 'raw_input_bytes' + self.write_to_pane_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( @@ -1670,7 +1704,7 @@ impl Tab { .get_pane_id_at(position, false) .with_context(err_context)?; 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)?; return Ok(()); } @@ -1680,7 +1714,7 @@ impl Tab { .get_pane_id_at(position, false) .with_context(err_context)?; 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)?; return Ok(()); } @@ -1689,7 +1723,9 @@ impl Tab { pub fn write_to_pane_id( &mut self, - input_bytes: Vec, + key_with_modifier: &Option, + raw_input_bytes: Vec, + raw_input_bytes_are_kitty: bool, pane_id: PaneId, client_id: Option, ) -> Result { @@ -1720,7 +1756,11 @@ impl Tab { match pane_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)) => { self.senders .send_to_pty_writer(PtyWriteInstruction::Write( @@ -1758,12 +1798,26 @@ impl Tab { 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)) => { let mut plugin_updates = vec![]; for key in parse_keys(&adjusted_input) { plugin_updates.push((Some(pid), client_id, Event::Key(key))); } + log::info!("plugin_updates: {:?}", plugin_updates); self.senders .send_to_plugin(PluginInstruction::Update(plugin_updates)) .with_context(err_context)?; @@ -1787,6 +1841,32 @@ impl Tab { } Ok(should_update_ui) } + pub fn write_to_pane_id_without_preprocessing( + &mut self, + raw_input_bytes: Vec, + pane_id: PaneId, + ) -> Result { + // 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( &self, client_id: ClientId, @@ -2889,8 +2969,13 @@ impl Tab { let relative_position = pane.relative_position(position); if let Some(mouse_event) = pane.mouse_left_click(&relative_position, false) { if !pane.position_is_on_frame(position) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &None, + mouse_event.into_bytes(), + false, + client_id, + ) + .with_context(err_context)?; } } else { pane.start_selection(&relative_position, client_id); @@ -2919,8 +3004,13 @@ impl Tab { let relative_position = pane.relative_position(position); if let Some(mouse_event) = pane.mouse_right_click(&relative_position, false) { if !pane.position_is_on_frame(position) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &None, + mouse_event.into_bytes(), + false, + client_id, + ) + .with_context(err_context)?; } } else { pane.handle_right_click(&relative_position, client_id); @@ -2946,8 +3036,13 @@ impl Tab { let relative_position = pane.relative_position(position); if let Some(mouse_event) = pane.mouse_middle_click(&relative_position, false) { if !pane.position_is_on_frame(position) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &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) { - 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)?; } } @@ -3039,7 +3134,7 @@ impl Tab { ); 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)?; } } @@ -3084,7 +3179,7 @@ impl Tab { ); 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)?; } else { let relative_position = active_pane.relative_position(position); @@ -3163,8 +3258,13 @@ impl Tab { .min(active_pane.get_content_rows() as isize), ); if let Some(mouse_event) = active_pane.mouse_left_click(&relative_position, true) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &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 } } else if selecting { @@ -3210,8 +3310,13 @@ impl Tab { .min(active_pane.get_content_rows() as isize), ); if let Some(mouse_event) = active_pane.mouse_right_click(&relative_position, true) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &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 } } @@ -3254,8 +3359,13 @@ impl Tab { ); if let Some(mouse_event) = active_pane.mouse_middle_click(&relative_position, true) { - self.write_to_active_terminal(mouse_event.into_bytes(), client_id) - .with_context(err_context)?; + self.write_to_active_terminal( + &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 } } diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index f8953235..ba62daa0 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -226,6 +226,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -251,6 +252,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -303,6 +305,7 @@ fn create_new_tab_with_swap_layouts( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -328,6 +331,7 @@ fn create_new_tab_with_swap_layouts( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let ( base_layout, @@ -382,6 +386,7 @@ fn create_new_tab_with_os_api( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -407,6 +412,7 @@ fn create_new_tab_with_os_api( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -447,6 +453,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -472,6 +479,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let pane_ids = tab_layout .extract_run_instructions() @@ -526,6 +534,7 @@ fn create_new_tab_with_mock_pty_writer( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -551,6 +560,7 @@ fn create_new_tab_with_mock_pty_writer( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -596,6 +606,7 @@ fn create_new_tab_with_sixel_support( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -621,6 +632,7 @@ fn create_new_tab_with_sixel_support( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -659,6 +671,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( rows, columns, @@ -671,6 +684,7 @@ fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { @@ -694,6 +708,7 @@ fn take_snapshot_with_sixel( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( rows, columns, @@ -706,6 +721,7 @@ fn take_snapshot_with_sixel( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut vte_parser = vte::Parser::new(); for &byte in ansi_instructions.as_bytes() { @@ -726,6 +742,7 @@ fn take_snapshot_and_cursor_position( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( rows, columns, @@ -738,6 +755,7 @@ fn take_snapshot_and_cursor_position( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut vte_parser = vte::Parser::new(); 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_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(); - 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(); - tab.write_to_active_terminal(bracketed_paste_end, client_id) + tab.write_to_active_terminal(&None, bracketed_paste_end, false, client_id) .unwrap(); pty_instruction_bus.exit(); diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index d1a3c020..5ad917ea 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -167,6 +167,7 @@ fn create_new_tab(size: Size) -> Tab { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -192,6 +193,7 @@ fn create_new_tab(size: Size) -> Tab { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -229,6 +231,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -254,6 +257,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let mut new_terminal_ids = vec![]; for i in 0..layout.extract_run_instructions().len() { @@ -297,6 +301,7 @@ fn create_new_tab_with_cell_size( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut tab = Tab::new( index, position, @@ -322,6 +327,7 @@ fn create_new_tab_with_cell_size( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); tab.apply_layout( TiledPaneLayout::default(), @@ -352,8 +358,14 @@ fn write_to_suppressed_pane() { // Make sure it's suppressed now tab.suppressed_panes.get(&PaneId::Terminal(2)).unwrap(); // Write content to it - tab.write_to_pane_id(vec![34, 127, 31, 82, 17, 182], PaneId::Terminal(2), None) - .unwrap(); + tab.write_to_pane_id( + &None, + vec![34, 127, 31, 82, 17, 182], + false, + PaneId::Terminal(2), + None, + ) + .unwrap(); } #[test] diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 50df6a29..bb26b5f4 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -70,6 +70,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>( let debug = false; let arrow_fonts = true; let styled_underlines = true; + let explicitly_disable_kitty_keyboard_protocol = false; let mut grid = Grid::new( screen_size.rows, screen_size.cols, @@ -82,6 +83,7 @@ fn take_snapshots_and_cursor_coordinates_from_render_events<'a>( debug, arrow_fonts, styled_underlines, + explicitly_disable_kitty_keyboard_protocol, ); let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events .filter_map(|server_instruction| { @@ -252,6 +254,7 @@ fn create_new_screen(size: Size) -> Screen { let debug = false; let styled_underlines = true; let arrow_fonts = true; + let explicitly_disable_kitty_keyboard_protocol = false; let screen = Screen::new( bus, &client_attributes, @@ -271,6 +274,7 @@ fn create_new_screen(size: Size) -> Screen { styled_underlines, arrow_fonts, layout_dir, + explicitly_disable_kitty_keyboard_protocol, ); screen } diff --git a/zellij-utils/Cargo.toml b/zellij-utils/Cargo.toml index 4cb9db77..94411a15 100644 --- a/zellij-utils/Cargo.toml +++ b/zellij-utils/Cargo.toml @@ -43,6 +43,7 @@ thiserror = "1.0.30" unicode-width = "0.1.8" url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.4.1", features = ["serde", "v4"] } +bitflags = "2.5.0" vte = { version = "0.11.0", default-features = false } #[cfg(not(target_family = "wasm"))] diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index 0d49686e..e830cd09 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -363,3 +363,8 @@ plugins { // Default: false // // 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 diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm index c4296d9f8fb0916da5460f6c4e83bb377d6cbe1e..e585f4ab79263096700b29cef5ad22c988bfc3c9 100755 GIT binary patch delta 122741 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 a@zurG9$!CvWAVL;Z(c8B{UP@O_5T2M#a?Rw delta 120658 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

&fW<}i=MRw*LVHO;#x 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 diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm index 0d0ce2c211017dc7e0a3a62450a2410ffe3cd69d..bb260003de2099099afdac8fea0b7ebffefc6454 100755 GIT binary patch delta 224589 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