mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-22 13:02:12 +03:00
feat(plugins): rebind keys at runtime (#3422)
* refactor(server): interpret keys on server so they can be rebound * feat(plugins): allow rebinding keys at runtime * various cleanups * add tests * style(fmt): rustfmt * fix(tests): address (some) e2e test flakiness * style(fmt): rustfmt
This commit is contained in:
parent
2ac8b15191
commit
1f0ae94f01
@ -56,6 +56,7 @@ impl ZellijPlugin for State {
|
||||
PermissionType::WebAccess,
|
||||
PermissionType::ReadCliPipes,
|
||||
PermissionType::MessageAndLaunchOtherPlugins,
|
||||
PermissionType::RebindKeys,
|
||||
]);
|
||||
self.configuration = configuration;
|
||||
subscribe(&[
|
||||
@ -318,6 +319,18 @@ impl ZellijPlugin for State {
|
||||
Some(std::path::PathBuf::from("/tmp")),
|
||||
);
|
||||
},
|
||||
BareKey::Char('0') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
|
||||
rebind_keys(
|
||||
"
|
||||
keybinds {
|
||||
locked {
|
||||
bind \"a\" { NewTab; }
|
||||
}
|
||||
}
|
||||
"
|
||||
.to_owned(),
|
||||
);
|
||||
},
|
||||
_ => {},
|
||||
},
|
||||
Event::CustomMessage(message, payload) => {
|
||||
|
@ -162,6 +162,7 @@ pub fn split_terminals_vertically() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
// back to normal mode after split
|
||||
step_is_complete = true;
|
||||
@ -205,6 +206,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(3, 2) {
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
// back to normal mode after split
|
||||
step_is_complete = true;
|
||||
@ -259,6 +261,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -286,6 +289,7 @@ pub fn scrolling_inside_a_pane() {
|
||||
{
|
||||
// all lines have been written to the pane
|
||||
remote_terminal.send_key(&SCROLL_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SCROLL_UP_IN_SCROLL_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -337,6 +341,7 @@ pub fn toggle_pane_fullscreen() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -350,6 +355,7 @@ pub fn toggle_pane_fullscreen() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -398,6 +404,7 @@ pub fn open_new_tab() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -411,6 +418,7 @@ pub fn open_new_tab() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -463,6 +471,7 @@ pub fn close_tab() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -476,6 +485,7 @@ pub fn close_tab() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -493,6 +503,7 @@ pub fn close_tab() {
|
||||
{
|
||||
// cursor is in the newly opened second tab
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&CLOSE_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -653,6 +664,7 @@ pub fn close_pane() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -666,6 +678,7 @@ pub fn close_pane() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -751,6 +764,7 @@ pub fn closing_last_pane_exits_zellij() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&CLOSE_PANE_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -795,6 +809,7 @@ pub fn typing_exit_closes_pane() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -807,9 +822,13 @@ pub fn typing_exit_closes_pane() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
remote_terminal.send_key("e".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("x".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("i".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("t".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("\n".as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -859,6 +878,7 @@ pub fn resize_pane() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -872,6 +892,7 @@ pub fn resize_pane() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&RESIZE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&RESIZE_LEFT_IN_RESIZE_MODE);
|
||||
// back to normal mode
|
||||
remote_terminal.send_key(&ENTER);
|
||||
@ -933,7 +954,9 @@ pub fn lock_mode() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("INTERFACE LOCKED") {
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("abc".as_bytes());
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -983,6 +1006,7 @@ pub fn resize_terminal_window() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1046,6 +1070,7 @@ pub fn detach_and_attach_session() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1070,6 +1095,7 @@ pub fn detach_and_attach_session() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.cursor_position_is(77, 2) {
|
||||
remote_terminal.send_key(&SESSION_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&DETACH_IN_SESSION_MODE);
|
||||
// text has been entered
|
||||
step_is_complete = true;
|
||||
@ -1286,6 +1312,7 @@ fn focus_pane_with_mouse() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1345,6 +1372,7 @@ pub fn scrolling_inside_a_pane_with_mouse() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1420,6 +1448,7 @@ pub fn start_without_pane_frames() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(2, 1)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1489,6 +1518,7 @@ pub fn mirrored_sessions() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1502,6 +1532,7 @@ pub fn mirrored_sessions() {
|
||||
if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1544,6 +1575,7 @@ pub fn mirrored_sessions() {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("some text") {
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&MOVE_FOCUS_LEFT_IN_PANE_MODE); // same key as tab mode
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1726,6 +1758,7 @@ pub fn multiple_users_in_different_panes_and_same_tab() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_PANE_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1817,6 +1850,7 @@ pub fn multiple_users_in_different_tabs() {
|
||||
if remote_terminal.cursor_position_is(3, 2) && remote_terminal.tip_appears() {
|
||||
// cursor is in the newly opened second pane
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1839,6 +1873,7 @@ pub fn multiple_users_in_different_tabs() {
|
||||
&& remote_terminal.snapshot_contains("Tab #1 [ ]")
|
||||
&& remote_terminal.snapshot_contains("Tab #2")
|
||||
&& remote_terminal.status_bar_appears()
|
||||
&& !remote_terminal.snapshot_contains("AND:")
|
||||
{
|
||||
// cursor is in the newly opened second tab
|
||||
step_is_complete = true;
|
||||
@ -1899,11 +1934,17 @@ pub fn bracketed_paste() {
|
||||
&& remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&BRACKETED_PASTE_START);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("a".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("b".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key("c".as_bytes());
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&BRACKETED_PASTE_END);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -1952,6 +1993,7 @@ pub fn toggle_floating_panes() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&TOGGLE_FLOATING_PANES);
|
||||
// back to normal mode after split
|
||||
step_is_complete = true;
|
||||
@ -2002,6 +2044,7 @@ pub fn tmux_mode() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&TMUX_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&SPLIT_RIGHT_IN_TMUX_MODE);
|
||||
// back to normal mode after split
|
||||
step_is_complete = true;
|
||||
@ -2050,6 +2093,7 @@ pub fn edit_scrollback() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&SCROLL_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&EDIT_SCROLLBACK);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -2099,8 +2143,13 @@ pub fn undo_rename_tab() {
|
||||
&& remote_terminal.snapshot_contains("Tab #1")
|
||||
{
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&RENAME_TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&[97, 97]);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&ESC);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&ESC);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -2113,7 +2162,9 @@ pub fn undo_rename_tab() {
|
||||
name: "Wait for tab name to apper on screen",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("Tab #1") {
|
||||
if remote_terminal.snapshot_contains("Tab #1")
|
||||
&& remote_terminal.snapshot_contains("Tip:")
|
||||
{
|
||||
step_is_complete = true
|
||||
}
|
||||
step_is_complete
|
||||
@ -2149,8 +2200,13 @@ pub fn undo_rename_pane() {
|
||||
if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2)
|
||||
{
|
||||
remote_terminal.send_key(&PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&RENAME_PANE_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&[97, 97]);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&ESC);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&ESC);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -2163,7 +2219,9 @@ pub fn undo_rename_pane() {
|
||||
name: "Wait for pane name to apper on screen",
|
||||
instruction: |remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("Pane #1") {
|
||||
if remote_terminal.snapshot_contains("Pane #1")
|
||||
&& remote_terminal.snapshot_contains("Tip:")
|
||||
{
|
||||
step_is_complete = true
|
||||
}
|
||||
step_is_complete
|
||||
@ -2212,6 +2270,7 @@ pub fn send_command_through_the_cli() {
|
||||
"{}/append-echo-script.sh",
|
||||
fixture_folder
|
||||
));
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
@ -2221,11 +2280,7 @@ pub fn send_command_through_the_cli() {
|
||||
name: "Initial run of suspended command",
|
||||
instruction: |mut remote_terminal: RemoteTerminal| -> bool {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.snapshot_contains("<Ctrl-c>")
|
||||
&& remote_terminal.cursor_position_is(0, 0)
|
||||
// cursor does not appear in
|
||||
// suspend_start panes
|
||||
{
|
||||
if remote_terminal.snapshot_contains("<Ctrl-c>") {
|
||||
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
|
||||
|
@ -261,7 +261,7 @@ fn read_from_channel(
|
||||
break;
|
||||
}
|
||||
if should_sleep {
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
should_sleep = false;
|
||||
}
|
||||
let mut buf = [0u8; 1280000];
|
||||
|
@ -11,6 +11,7 @@ pub fn new_tab() -> Step {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() {
|
||||
remote_terminal.send_key(&TAB_MODE);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
remote_terminal.send_key(&NEW_TAB_IN_TAB_MODE);
|
||||
step_is_complete = true;
|
||||
}
|
||||
@ -37,6 +38,7 @@ pub fn move_tab_left() -> Step {
|
||||
let mut step_is_complete = false;
|
||||
if remote_terminal.tip_appears() && remote_terminal.status_bar_appears() {
|
||||
remote_terminal.send_key(&MOVE_TAB_LEFT);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
step_is_complete = true;
|
||||
}
|
||||
step_is_complete
|
||||
|
@ -146,9 +146,6 @@ impl InputHandler {
|
||||
)) => {
|
||||
self.handle_key(&key_with_modifier, raw_bytes, true);
|
||||
},
|
||||
Ok((InputInstruction::SwitchToMode(input_mode), _error_context)) => {
|
||||
self.mode = input_mode;
|
||||
},
|
||||
Ok((
|
||||
InputInstruction::AnsiStdinInstructions(ansi_stdin_instructions),
|
||||
_error_context,
|
||||
@ -180,18 +177,13 @@ impl InputHandler {
|
||||
raw_bytes: Vec<u8>,
|
||||
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,
|
||||
// we interpret the keys into actions on the server side so that we can change the
|
||||
// keybinds at runtime
|
||||
self.os_input.send_to_server(ClientToServerMsg::Key(
|
||||
key.clone(),
|
||||
raw_bytes,
|
||||
is_kitty_keyboard_protocol,
|
||||
) {
|
||||
let should_exit = self.dispatch_action(action, None);
|
||||
if should_exit {
|
||||
self.should_exit = true;
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
fn handle_stdin_ansi_instruction(&mut self, ansi_stdin_instructions: AnsiStdinInstruction) {
|
||||
match ansi_stdin_instructions {
|
||||
@ -316,14 +308,8 @@ impl InputHandler {
|
||||
self.exit(ExitReason::NormalDetached);
|
||||
should_break = true;
|
||||
},
|
||||
Action::SwitchToMode(mode) => {
|
||||
// this is an optimistic update, we should get a SwitchMode instruction from the
|
||||
// server later that atomically changes the mode as well
|
||||
self.mode = mode;
|
||||
self.os_input
|
||||
.send_to_server(ClientToServerMsg::Action(action, None, None));
|
||||
},
|
||||
Action::CloseFocus
|
||||
| Action::SwitchToMode(..)
|
||||
| Action::ClearScreen
|
||||
| Action::NewPane(..)
|
||||
| Action::Run(_)
|
||||
|
@ -25,7 +25,7 @@ use crate::{
|
||||
use zellij_utils::{
|
||||
channels::{self, ChannelWithContext, SenderWithContext},
|
||||
consts::{set_permissions, ZELLIJ_SOCK_DIR},
|
||||
data::{ClientId, ConnectToSession, InputMode, KeyWithModifier, Style},
|
||||
data::{ClientId, ConnectToSession, KeyWithModifier, Style},
|
||||
envs,
|
||||
errors::{ClientContext, ContextType, ErrorInstruction},
|
||||
input::{config::Config, options::Options},
|
||||
@ -42,7 +42,6 @@ pub(crate) enum ClientInstruction {
|
||||
Render(String),
|
||||
UnblockInputThread,
|
||||
Exit(ExitReason),
|
||||
SwitchToMode(InputMode),
|
||||
Connected,
|
||||
ActiveClients(Vec<ClientId>),
|
||||
StartedParsingStdinQuery,
|
||||
@ -62,9 +61,6 @@ impl From<ServerToClientMsg> for ClientInstruction {
|
||||
ServerToClientMsg::Exit(e) => ClientInstruction::Exit(e),
|
||||
ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
|
||||
ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
|
||||
ServerToClientMsg::SwitchToMode(input_mode) => {
|
||||
ClientInstruction::SwitchToMode(input_mode)
|
||||
},
|
||||
ServerToClientMsg::Connected => ClientInstruction::Connected,
|
||||
ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
|
||||
ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
|
||||
@ -90,7 +86,6 @@ impl From<&ClientInstruction> for ClientContext {
|
||||
ClientInstruction::Error(_) => ClientContext::Error,
|
||||
ClientInstruction::Render(_) => ClientContext::Render,
|
||||
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
|
||||
ClientInstruction::SwitchToMode(_) => ClientContext::SwitchToMode,
|
||||
ClientInstruction::Connected => ClientContext::Connected,
|
||||
ClientInstruction::ActiveClients(_) => ClientContext::ActiveClients,
|
||||
ClientInstruction::Log(_) => ClientContext::Log,
|
||||
@ -154,7 +149,6 @@ impl ClientInfo {
|
||||
pub(crate) enum InputInstruction {
|
||||
KeyEvent(InputEvent, Vec<u8>),
|
||||
KeyWithModifierEvent(KeyWithModifier, Vec<u8>),
|
||||
SwitchToMode(InputMode),
|
||||
AnsiStdinInstructions(Vec<AnsiStdinInstruction>),
|
||||
StartedParsing,
|
||||
DoneParsing,
|
||||
@ -505,11 +499,6 @@ pub fn start_client(
|
||||
ClientInstruction::UnblockInputThread => {
|
||||
command_is_executing.unblock_input_thread();
|
||||
},
|
||||
ClientInstruction::SwitchToMode(input_mode) => {
|
||||
send_input_instructions
|
||||
.send(InputInstruction::SwitchToMode(input_mode))
|
||||
.unwrap();
|
||||
},
|
||||
ClientInstruction::Log(lines_to_log) => {
|
||||
for line in lines_to_log {
|
||||
log::info!("{line}");
|
||||
@ -634,7 +623,3 @@ pub fn start_server_detached(
|
||||
os_input.connect_to_server(&*ipc_pipe);
|
||||
os_input.send_to_server(first_msg);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./unit/stdin_tests.rs"]
|
||||
mod stdin_tests;
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,320 +0,0 @@
|
||||
use super::input_loop;
|
||||
use crate::stdin_ansi_parser::StdinAnsiParser;
|
||||
use crate::stdin_loop;
|
||||
use zellij_utils::anyhow::Result;
|
||||
use zellij_utils::data::{Direction, InputMode, Palette};
|
||||
use zellij_utils::input::actions::Action;
|
||||
use zellij_utils::input::config::Config;
|
||||
use zellij_utils::input::options::Options;
|
||||
use zellij_utils::nix;
|
||||
use zellij_utils::pane_size::Size;
|
||||
use zellij_utils::termwiz::input::{InputEvent, KeyCode, KeyEvent, Modifiers};
|
||||
|
||||
use crate::InputInstruction;
|
||||
use crate::{
|
||||
os_input_output::{ClientOsApi, StdinPoller},
|
||||
ClientInstruction, CommandIsExecuting,
|
||||
};
|
||||
|
||||
use ::insta::assert_snapshot;
|
||||
use std::path::Path;
|
||||
|
||||
use std::io;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use zellij_utils::{
|
||||
errors::ErrorContext,
|
||||
ipc::{ClientToServerMsg, ServerToClientMsg},
|
||||
};
|
||||
|
||||
use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext};
|
||||
|
||||
fn read_fixture(fixture_name: &str) -> Vec<u8> {
|
||||
let mut path_to_file = std::path::PathBuf::new();
|
||||
path_to_file.push("../src");
|
||||
path_to_file.push("tests");
|
||||
path_to_file.push("fixtures");
|
||||
path_to_file.push(fixture_name);
|
||||
std::fs::read(path_to_file)
|
||||
.unwrap_or_else(|_| panic!("could not read fixture {:?}", &fixture_name))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub mod commands {
|
||||
pub const QUIT: [u8; 1] = [17]; // ctrl-q
|
||||
pub const ESC: [u8; 1] = [27];
|
||||
pub const ENTER: [u8; 1] = [10]; // char '\n'
|
||||
|
||||
pub const MOVE_FOCUS_LEFT_IN_NORMAL_MODE: [u8; 2] = [27, 104]; // alt-h
|
||||
pub const MOVE_FOCUS_RIGHT_IN_NORMAL_MODE: [u8; 2] = [27, 108]; // alt-l
|
||||
|
||||
pub const PANE_MODE: [u8; 1] = [16]; // ctrl-p
|
||||
pub const SPAWN_TERMINAL_IN_PANE_MODE: [u8; 1] = [110]; // n
|
||||
pub const MOVE_FOCUS_IN_PANE_MODE: [u8; 1] = [112]; // p
|
||||
pub const SPLIT_DOWN_IN_PANE_MODE: [u8; 1] = [100]; // d
|
||||
pub const SPLIT_RIGHT_IN_PANE_MODE: [u8; 1] = [114]; // r
|
||||
pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f
|
||||
pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x
|
||||
pub const MOVE_FOCUS_DOWN_IN_PANE_MODE: [u8; 1] = [106]; // j
|
||||
pub const MOVE_FOCUS_UP_IN_PANE_MODE: [u8; 1] = [107]; // k
|
||||
pub const MOVE_FOCUS_LEFT_IN_PANE_MODE: [u8; 1] = [104]; // h
|
||||
pub const MOVE_FOCUS_RIGHT_IN_PANE_MODE: [u8; 1] = [108]; // l
|
||||
|
||||
pub const SCROLL_MODE: [u8; 1] = [19]; // ctrl-s
|
||||
pub const SCROLL_UP_IN_SCROLL_MODE: [u8; 1] = [107]; // k
|
||||
pub const SCROLL_DOWN_IN_SCROLL_MODE: [u8; 1] = [106]; // j
|
||||
pub const SCROLL_PAGE_UP_IN_SCROLL_MODE: [u8; 1] = [2]; // ctrl-b
|
||||
pub const SCROLL_PAGE_DOWN_IN_SCROLL_MODE: [u8; 1] = [6]; // ctrl-f
|
||||
|
||||
pub const RESIZE_MODE: [u8; 1] = [18]; // ctrl-r
|
||||
pub const RESIZE_DOWN_IN_RESIZE_MODE: [u8; 1] = [106]; // j
|
||||
pub const RESIZE_UP_IN_RESIZE_MODE: [u8; 1] = [107]; // k
|
||||
pub const RESIZE_LEFT_IN_RESIZE_MODE: [u8; 1] = [104]; // h
|
||||
pub const RESIZE_RIGHT_IN_RESIZE_MODE: [u8; 1] = [108]; // l
|
||||
|
||||
pub const TAB_MODE: [u8; 1] = [20]; // ctrl-t
|
||||
pub const NEW_TAB_IN_TAB_MODE: [u8; 1] = [110]; // n
|
||||
pub const SWITCH_NEXT_TAB_IN_TAB_MODE: [u8; 1] = [108]; // l
|
||||
pub const SWITCH_PREV_TAB_IN_TAB_MODE: [u8; 1] = [104]; // h
|
||||
pub const CLOSE_TAB_IN_TAB_MODE: [u8; 1] = [120]; // x
|
||||
|
||||
pub const BRACKETED_PASTE_START: [u8; 6] = [27, 91, 50, 48, 48, 126]; // \u{1b}[200~
|
||||
pub const BRACKETED_PASTE_END: [u8; 6] = [27, 91, 50, 48, 49, 126]; // \u{1b}[201
|
||||
pub const SLEEP: [u8; 0] = [];
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct FakeStdoutWriter {
|
||||
buffer: Arc<Mutex<Vec<u8>>>,
|
||||
}
|
||||
impl FakeStdoutWriter {
|
||||
pub fn new(buffer: Arc<Mutex<Vec<u8>>>) -> Self {
|
||||
FakeStdoutWriter { buffer }
|
||||
}
|
||||
}
|
||||
impl io::Write for FakeStdoutWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
|
||||
self.buffer.lock().unwrap().extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FakeClientOsApi {
|
||||
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
|
||||
command_is_executing: Arc<Mutex<CommandIsExecuting>>,
|
||||
stdout_buffer: Arc<Mutex<Vec<u8>>>,
|
||||
stdin_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl FakeClientOsApi {
|
||||
pub fn new(
|
||||
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
|
||||
command_is_executing: CommandIsExecuting,
|
||||
) -> Self {
|
||||
// while command_is_executing itself is implemented with an Arc<Mutex>, we have to have an
|
||||
// Arc<Mutex> here because we need interior mutability, otherwise we'll have to change the
|
||||
// ClientOsApi trait, and that will cause a lot of havoc
|
||||
let command_is_executing = Arc::new(Mutex::new(command_is_executing));
|
||||
let stdout_buffer = Arc::new(Mutex::new(vec![]));
|
||||
FakeClientOsApi {
|
||||
events_sent_to_server,
|
||||
command_is_executing,
|
||||
stdout_buffer,
|
||||
stdin_buffer: vec![],
|
||||
}
|
||||
}
|
||||
pub fn with_stdin_buffer(mut self, stdin_buffer: Vec<u8>) -> Self {
|
||||
self.stdin_buffer = stdin_buffer;
|
||||
self
|
||||
}
|
||||
pub fn stdout_buffer(&self) -> Vec<u8> {
|
||||
self.stdout_buffer.lock().unwrap().drain(..).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientOsApi for FakeClientOsApi {
|
||||
fn get_terminal_size_using_fd(&self, _fd: RawFd) -> Size {
|
||||
unimplemented!()
|
||||
}
|
||||
fn set_raw_mode(&mut self, _fd: RawFd) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn unset_raw_mode(&self, _fd: RawFd) -> Result<(), nix::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write> {
|
||||
let fake_stdout_writer = FakeStdoutWriter::new(self.stdout_buffer.clone());
|
||||
Box::new(fake_stdout_writer)
|
||||
}
|
||||
fn get_stdin_reader(&self) -> Box<dyn io::BufRead> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn update_session_name(&mut self, _new_session_name: String) {}
|
||||
fn read_from_stdin(&mut self) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(self.stdin_buffer.drain(..).collect())
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn send_to_server(&self, msg: ClientToServerMsg) {
|
||||
{
|
||||
let mut events_sent_to_server = self.events_sent_to_server.lock().unwrap();
|
||||
events_sent_to_server.push(msg);
|
||||
}
|
||||
{
|
||||
let mut command_is_executing = self.command_is_executing.lock().unwrap();
|
||||
command_is_executing.unblock_input_thread();
|
||||
}
|
||||
}
|
||||
fn recv_from_server(&self) -> Option<(ServerToClientMsg, ErrorContext)> {
|
||||
unimplemented!()
|
||||
}
|
||||
fn handle_signals(&self, _sigwinch_cb: Box<dyn Fn()>, _quit_cb: Box<dyn Fn()>) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn connect_to_server(&self, _path: &Path) {
|
||||
unimplemented!()
|
||||
}
|
||||
fn load_palette(&self) -> Palette {
|
||||
unimplemented!()
|
||||
}
|
||||
fn enable_mouse(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn disable_mouse(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn stdin_poller(&self) -> StdinPoller {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_actions_sent_to_server(
|
||||
events_sent_to_server: Arc<Mutex<Vec<ClientToServerMsg>>>,
|
||||
) -> Vec<Action> {
|
||||
let events_sent_to_server = events_sent_to_server.lock().unwrap();
|
||||
events_sent_to_server.iter().fold(vec![], |mut acc, event| {
|
||||
if let ClientToServerMsg::Action(action, None, None) = event {
|
||||
acc.push(action.clone());
|
||||
}
|
||||
acc
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn quit_breaks_input_loop() {
|
||||
let stdin_events = vec![(
|
||||
commands::QUIT.to_vec(),
|
||||
InputEvent::Key(KeyEvent {
|
||||
key: KeyCode::Char('q'),
|
||||
modifiers: Modifiers::CTRL,
|
||||
}),
|
||||
)];
|
||||
let events_sent_to_server = Arc::new(Mutex::new(vec![]));
|
||||
let command_is_executing = CommandIsExecuting::new();
|
||||
let client_os_api = Box::new(FakeClientOsApi::new(
|
||||
events_sent_to_server.clone(),
|
||||
command_is_executing.clone(),
|
||||
));
|
||||
let config = Config::from_default_assets().unwrap();
|
||||
let options = Options::default();
|
||||
|
||||
let (send_client_instructions, _receive_client_instructions): ChannelWithContext<
|
||||
ClientInstruction,
|
||||
> = channels::bounded(50);
|
||||
let send_client_instructions = SenderWithContext::new(send_client_instructions);
|
||||
|
||||
let (send_input_instructions, receive_input_instructions): ChannelWithContext<
|
||||
InputInstruction,
|
||||
> = channels::bounded(50);
|
||||
let send_input_instructions = SenderWithContext::new(send_input_instructions);
|
||||
for event in stdin_events {
|
||||
send_input_instructions
|
||||
.send(InputInstruction::KeyEvent(event.1, event.0))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let default_mode = InputMode::Normal;
|
||||
input_loop(
|
||||
client_os_api,
|
||||
config,
|
||||
options,
|
||||
command_is_executing,
|
||||
send_client_instructions,
|
||||
default_mode,
|
||||
receive_input_instructions,
|
||||
);
|
||||
let expected_actions_sent_to_server = vec![Action::Quit];
|
||||
let received_actions = extract_actions_sent_to_server(events_sent_to_server);
|
||||
assert_eq!(
|
||||
expected_actions_sent_to_server, received_actions,
|
||||
"All actions sent to server properly"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn move_focus_left_in_normal_mode() {
|
||||
let stdin_events = vec![
|
||||
(
|
||||
commands::MOVE_FOCUS_LEFT_IN_NORMAL_MODE.to_vec(),
|
||||
InputEvent::Key(KeyEvent {
|
||||
key: KeyCode::Char('h'),
|
||||
modifiers: Modifiers::ALT,
|
||||
}),
|
||||
),
|
||||
(
|
||||
commands::QUIT.to_vec(),
|
||||
InputEvent::Key(KeyEvent {
|
||||
key: KeyCode::Char('q'),
|
||||
modifiers: Modifiers::CTRL,
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
let events_sent_to_server = Arc::new(Mutex::new(vec![]));
|
||||
let command_is_executing = CommandIsExecuting::new();
|
||||
let client_os_api = Box::new(FakeClientOsApi::new(
|
||||
events_sent_to_server.clone(),
|
||||
command_is_executing.clone(),
|
||||
));
|
||||
let config = Config::from_default_assets().unwrap();
|
||||
let options = Options::default();
|
||||
|
||||
let (send_client_instructions, _receive_client_instructions): ChannelWithContext<
|
||||
ClientInstruction,
|
||||
> = channels::bounded(50);
|
||||
let send_client_instructions = SenderWithContext::new(send_client_instructions);
|
||||
|
||||
let (send_input_instructions, receive_input_instructions): ChannelWithContext<
|
||||
InputInstruction,
|
||||
> = channels::bounded(50);
|
||||
let send_input_instructions = SenderWithContext::new(send_input_instructions);
|
||||
for event in stdin_events {
|
||||
send_input_instructions
|
||||
.send(InputInstruction::KeyEvent(event.1, event.0))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let default_mode = InputMode::Normal;
|
||||
input_loop(
|
||||
client_os_api,
|
||||
config,
|
||||
options,
|
||||
command_is_executing,
|
||||
send_client_instructions,
|
||||
default_mode,
|
||||
receive_input_instructions,
|
||||
);
|
||||
let expected_actions_sent_to_server =
|
||||
vec![Action::MoveFocusOrTab(Direction::Left), Action::Quit];
|
||||
let received_actions = extract_actions_sent_to_server(events_sent_to_server);
|
||||
assert_eq!(
|
||||
expected_actions_sent_to_server, received_actions,
|
||||
"All actions sent to server properly"
|
||||
);
|
||||
}
|
@ -42,12 +42,13 @@ use zellij_utils::{
|
||||
channels::{self, ChannelWithContext, SenderWithContext},
|
||||
cli::CliArgs,
|
||||
consts::{DEFAULT_SCROLL_BUFFER_SIZE, SCROLL_BUFFER_SIZE},
|
||||
data::{ConnectToSession, Event, PluginCapabilities},
|
||||
data::{ConnectToSession, Event, InputMode, PluginCapabilities},
|
||||
errors::{prelude::*, ContextType, ErrorInstruction, FatalError, ServerContext},
|
||||
home::{default_layout_dir, get_default_data_dir},
|
||||
input::{
|
||||
command::{RunCommand, TerminalAction},
|
||||
get_mode_info,
|
||||
keybinds::Keybinds,
|
||||
layout::Layout,
|
||||
options::Options,
|
||||
plugins::PluginAliases,
|
||||
@ -94,6 +95,9 @@ pub enum ServerInstruction {
|
||||
client_id: ClientId,
|
||||
},
|
||||
DisconnectAllClientsExcept(ClientId),
|
||||
ChangeMode(ClientId, InputMode),
|
||||
ChangeModeForAllClients(InputMode),
|
||||
RebindKeys(ClientId, String), // String -> stringified keybindings
|
||||
}
|
||||
|
||||
impl From<&ServerInstruction> for ServerContext {
|
||||
@ -121,6 +125,11 @@ impl From<&ServerInstruction> for ServerContext {
|
||||
ServerInstruction::DisconnectAllClientsExcept(..) => {
|
||||
ServerContext::DisconnectAllClientsExcept
|
||||
},
|
||||
ServerInstruction::ChangeMode(..) => ServerContext::ChangeMode,
|
||||
ServerInstruction::ChangeModeForAllClients(..) => {
|
||||
ServerContext::ChangeModeForAllClients
|
||||
},
|
||||
ServerInstruction::RebindKeys(..) => ServerContext::RebindKeys,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,6 +147,8 @@ pub(crate) struct SessionMetaData {
|
||||
pub default_shell: Option<TerminalAction>,
|
||||
pub layout: Box<Layout>,
|
||||
pub config_options: Box<Options>,
|
||||
pub client_keybinds: HashMap<ClientId, Keybinds>,
|
||||
pub client_input_modes: HashMap<ClientId, InputMode>,
|
||||
screen_thread: Option<thread::JoinHandle<()>>,
|
||||
pty_thread: Option<thread::JoinHandle<()>>,
|
||||
plugin_thread: Option<thread::JoinHandle<()>>,
|
||||
@ -145,6 +156,56 @@ pub(crate) struct SessionMetaData {
|
||||
background_jobs_thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl SessionMetaData {
|
||||
pub fn set_client_keybinds(&mut self, client_id: ClientId, keybinds: Keybinds) {
|
||||
self.client_keybinds.insert(client_id, keybinds);
|
||||
self.client_input_modes.insert(
|
||||
client_id,
|
||||
self.config_options.default_mode.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
pub fn get_client_keybinds_and_mode(
|
||||
&self,
|
||||
client_id: &ClientId,
|
||||
) -> Option<(&Keybinds, &InputMode)> {
|
||||
match (
|
||||
self.client_keybinds.get(client_id),
|
||||
self.client_input_modes.get(client_id),
|
||||
) {
|
||||
(Some(client_keybinds), Some(client_input_mode)) => {
|
||||
Some((client_keybinds, client_input_mode))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn change_mode_for_all_clients(&mut self, input_mode: InputMode) {
|
||||
let all_clients: Vec<ClientId> = self.client_input_modes.keys().copied().collect();
|
||||
for client_id in all_clients {
|
||||
self.client_input_modes.insert(client_id, input_mode);
|
||||
}
|
||||
}
|
||||
pub fn rebind_keys(&mut self, client_id: ClientId, new_keybinds: String) -> Option<Keybinds> {
|
||||
if let Some(current_keybinds) = self.client_keybinds.get_mut(&client_id) {
|
||||
match Keybinds::from_string(
|
||||
new_keybinds,
|
||||
current_keybinds.clone(),
|
||||
&self.config_options,
|
||||
) {
|
||||
Ok(new_keybinds) => {
|
||||
*current_keybinds = new_keybinds.clone();
|
||||
return Some(new_keybinds);
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to parse keybindings: {}", e);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to bind keys for client: {client_id}");
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SessionMetaData {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.senders.send_to_pty(PtyInstruction::Exit);
|
||||
@ -383,6 +444,12 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||
plugin_aliases,
|
||||
);
|
||||
*session_data.write().unwrap() = Some(session);
|
||||
session_data
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_client_keybinds(client_id, client_attributes.keybinds.clone());
|
||||
session_state
|
||||
.write()
|
||||
.unwrap()
|
||||
@ -469,8 +536,9 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||
pane_id_to_focus,
|
||||
client_id,
|
||||
) => {
|
||||
let rlock = session_data.read().unwrap();
|
||||
let session_data = rlock.as_ref().unwrap();
|
||||
let mut rlock = session_data.write().unwrap();
|
||||
let session_data = rlock.as_mut().unwrap();
|
||||
session_data.set_client_keybinds(client_id, attrs.keybinds.clone());
|
||||
session_state
|
||||
.write()
|
||||
.unwrap()
|
||||
@ -498,7 +566,6 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||
.unwrap();
|
||||
let default_mode = options.default_mode.unwrap_or_default();
|
||||
let mode_info = get_mode_info(default_mode, &attrs, session_data.capabilities);
|
||||
let mode = mode_info.mode;
|
||||
session_data
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone(), client_id))
|
||||
@ -511,12 +578,6 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||
Event::ModeUpdate(mode_info),
|
||||
)]))
|
||||
.unwrap();
|
||||
send_to_client!(
|
||||
client_id,
|
||||
os_input,
|
||||
ServerToClientMsg::SwitchToMode(mode),
|
||||
session_state
|
||||
);
|
||||
},
|
||||
ServerInstruction::UnblockInputThread => {
|
||||
let client_ids = session_state.read().unwrap().client_ids();
|
||||
@ -827,6 +888,42 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, socket_path: PathBuf) {
|
||||
.unwrap()
|
||||
.associate_pipe_with_client(pipe_id, client_id);
|
||||
},
|
||||
ServerInstruction::ChangeMode(client_id, input_mode) => {
|
||||
session_data
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.client_input_modes
|
||||
.insert(client_id, input_mode);
|
||||
},
|
||||
ServerInstruction::ChangeModeForAllClients(input_mode) => {
|
||||
session_data
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.change_mode_for_all_clients(input_mode);
|
||||
},
|
||||
ServerInstruction::RebindKeys(client_id, new_keybinds) => {
|
||||
let new_keybinds = session_data
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.rebind_keys(client_id, new_keybinds)
|
||||
.clone();
|
||||
if let Some(new_keybinds) = new_keybinds {
|
||||
session_data
|
||||
.write()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.senders
|
||||
.send_to_screen(ScreenInstruction::RebindKeys(new_keybinds, client_id))
|
||||
.unwrap();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1054,6 +1151,8 @@ fn init_session(
|
||||
client_attributes,
|
||||
layout,
|
||||
config_options: config_options.clone(),
|
||||
client_keybinds: HashMap::new(),
|
||||
client_input_modes: HashMap::new(),
|
||||
screen_thread: Some(screen_thread),
|
||||
pty_thread: Some(pty_thread),
|
||||
plugin_thread: Some(plugin_thread),
|
||||
|
@ -6485,6 +6485,87 @@ pub fn disconnect_other_clients_plugins_command() {
|
||||
assert_snapshot!(format!("{:#?}", switch_session_event));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn rebind_keys_plugin_command() {
|
||||
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
|
||||
// destructor removes the directory
|
||||
let plugin_host_folder = PathBuf::from(temp_folder.path());
|
||||
let cache_path = plugin_host_folder.join("permissions_test.kdl");
|
||||
let (plugin_thread_sender, server_receiver, screen_receiver, teardown) =
|
||||
create_plugin_thread_with_server_receiver(Some(plugin_host_folder));
|
||||
let plugin_should_float = Some(false);
|
||||
let plugin_title = Some("test_plugin".to_owned());
|
||||
let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
|
||||
_allow_exec_host_cmd: false,
|
||||
location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
|
||||
configuration: Default::default(),
|
||||
..Default::default()
|
||||
});
|
||||
let tab_index = 1;
|
||||
let client_id = 1;
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let received_screen_instructions = Arc::new(Mutex::new(vec![]));
|
||||
let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
|
||||
received_screen_instructions,
|
||||
ScreenInstruction::Exit,
|
||||
screen_receiver,
|
||||
1,
|
||||
&PermissionType::ChangeApplicationState,
|
||||
cache_path,
|
||||
plugin_thread_sender,
|
||||
client_id
|
||||
);
|
||||
let received_server_instruction = Arc::new(Mutex::new(vec![]));
|
||||
let server_thread = log_actions_in_thread!(
|
||||
received_server_instruction,
|
||||
ServerInstruction::RebindKeys,
|
||||
server_receiver,
|
||||
1
|
||||
);
|
||||
|
||||
let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
|
||||
let _ = plugin_thread_sender.send(PluginInstruction::Load(
|
||||
plugin_should_float,
|
||||
false,
|
||||
plugin_title,
|
||||
run_plugin,
|
||||
tab_index,
|
||||
None,
|
||||
client_id,
|
||||
size,
|
||||
None,
|
||||
false,
|
||||
));
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
|
||||
let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
|
||||
None,
|
||||
Some(client_id),
|
||||
Event::Key(KeyWithModifier::new(BareKey::Char('0')).with_ctrl_modifier()), // this triggers the enent in the fixture plugin
|
||||
)]));
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
teardown();
|
||||
server_thread.join().unwrap(); // this might take a while if the cache is cold
|
||||
let rebind_keys_event = received_server_instruction
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|i| {
|
||||
if let ServerInstruction::RebindKeys(..) = i {
|
||||
Some(i.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.clone();
|
||||
assert_snapshot!(format!("{:#?}", rebind_keys_event));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
pub fn run_plugin_in_specific_cwd() {
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||
assertion_line: 5307
|
||||
assertion_line: 5500
|
||||
expression: "format!(\"{:#?}\", permissions)"
|
||||
---
|
||||
Some(
|
||||
@ -14,5 +14,6 @@ Some(
|
||||
WebAccess,
|
||||
ReadCliPipes,
|
||||
MessageAndLaunchOtherPlugins,
|
||||
RebindKeys,
|
||||
],
|
||||
)
|
||||
|
@ -0,0 +1,11 @@
|
||||
---
|
||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||
assertion_line: 6566
|
||||
expression: "format!(\"{:#?}\", rebind_keys_event)"
|
||||
---
|
||||
Some(
|
||||
RebindKeys(
|
||||
1,
|
||||
"\n keybinds {\n locked {\n bind \"a\" { NewTab; }\n }\n }\n ",
|
||||
),
|
||||
)
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-server/src/plugins/./unit/plugin_tests.rs
|
||||
assertion_line: 5217
|
||||
assertion_line: 5409
|
||||
expression: "format!(\"{:#?}\", new_tab_event)"
|
||||
---
|
||||
Some(
|
||||
@ -16,5 +16,6 @@ Some(
|
||||
WebAccess,
|
||||
ReadCliPipes,
|
||||
MessageAndLaunchOtherPlugins,
|
||||
RebindKeys,
|
||||
],
|
||||
)
|
||||
|
@ -268,6 +268,7 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
|
||||
PluginCommand::WatchFilesystem => watch_filesystem(env),
|
||||
PluginCommand::DumpSessionLayout => dump_session_layout(env),
|
||||
PluginCommand::CloseSelf => close_self(env),
|
||||
PluginCommand::RebindKeys(new_keybinds) => rebind_keys(env, new_keybinds)?,
|
||||
},
|
||||
(PermissionStatus::Denied, permission) => {
|
||||
log::error!(
|
||||
@ -842,6 +843,16 @@ fn close_self(env: &ForeignFunctionEnv) {
|
||||
.non_fatal();
|
||||
}
|
||||
|
||||
fn rebind_keys(env: &ForeignFunctionEnv, new_keybinds: String) -> Result<()> {
|
||||
let err_context = || "Failed to rebind keys";
|
||||
let client_id = env.plugin_env.client_id;
|
||||
env.plugin_env
|
||||
.senders
|
||||
.send_to_server(ServerInstruction::RebindKeys(client_id, new_keybinds))
|
||||
.with_context(err_context)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn switch_to_mode(env: &ForeignFunctionEnv, input_mode: InputMode) {
|
||||
let action = Action::SwitchToMode(input_mode);
|
||||
let error_msg = || {
|
||||
@ -1594,6 +1605,7 @@ fn check_command_permission(
|
||||
| PluginCommand::CliPipeOutput(..) => PermissionType::ReadCliPipes,
|
||||
PluginCommand::MessageToPlugin(..) => PermissionType::MessageAndLaunchOtherPlugins,
|
||||
PluginCommand::DumpSessionLayout => PermissionType::ReadApplicationState,
|
||||
PluginCommand::RebindKeys(..) => PermissionType::RebindKeys,
|
||||
_ => return (PermissionStatus::Granted, None),
|
||||
};
|
||||
|
||||
|
@ -101,6 +101,9 @@ pub(crate) fn route_action(
|
||||
Event::ModeUpdate(get_mode_info(mode, attrs, capabilities)),
|
||||
)]))
|
||||
.with_context(err_context)?;
|
||||
senders
|
||||
.send_to_server(ServerInstruction::ChangeMode(client_id, mode))
|
||||
.with_context(err_context)?;
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::ChangeMode(
|
||||
get_mode_info(mode, attrs, capabilities),
|
||||
@ -344,6 +347,11 @@ pub(crate) fn route_action(
|
||||
Event::ModeUpdate(get_mode_info(input_mode, attrs, capabilities)),
|
||||
)]))
|
||||
.with_context(err_context)?;
|
||||
|
||||
senders
|
||||
.send_to_server(ServerInstruction::ChangeModeForAllClients(input_mode))
|
||||
.with_context(err_context)?;
|
||||
|
||||
senders
|
||||
.send_to_screen(ScreenInstruction::ChangeModeForAllClients(get_mode_info(
|
||||
input_mode,
|
||||
@ -988,20 +996,42 @@ pub(crate) fn route_thread_main(
|
||||
-> Result<bool> {
|
||||
let mut should_break = false;
|
||||
match instruction {
|
||||
ClientToServerMsg::Key(key, raw_bytes, is_kitty_keyboard_protocol) => {
|
||||
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
||||
match rlocked_sessions.get_client_keybinds_and_mode(&client_id) {
|
||||
Some((keybinds, input_mode)) => {
|
||||
for action in keybinds
|
||||
.get_actions_for_key_in_mode_or_default_action(
|
||||
&input_mode,
|
||||
&key,
|
||||
raw_bytes,
|
||||
is_kitty_keyboard_protocol,
|
||||
)
|
||||
{
|
||||
if route_action(
|
||||
action,
|
||||
client_id,
|
||||
None,
|
||||
rlocked_sessions.senders.clone(),
|
||||
rlocked_sessions.capabilities.clone(),
|
||||
rlocked_sessions.client_attributes.clone(),
|
||||
rlocked_sessions.default_shell.clone(),
|
||||
rlocked_sessions.layout.clone(),
|
||||
Some(&mut seen_cli_pipes),
|
||||
)? {
|
||||
should_break = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
log::error!("Failed to get keybindings for client");
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
ClientToServerMsg::Action(action, maybe_pane_id, maybe_client_id) => {
|
||||
let client_id = maybe_client_id.unwrap_or(client_id);
|
||||
if let Some(rlocked_sessions) = rlocked_sessions.as_ref() {
|
||||
if let Action::SwitchToMode(input_mode) = action {
|
||||
let send_res = os_input.send_to_client(
|
||||
client_id,
|
||||
ServerToClientMsg::SwitchToMode(input_mode),
|
||||
);
|
||||
if send_res.is_err() {
|
||||
let _ = to_server
|
||||
.send(ServerInstruction::RemoveClient(client_id));
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
if route_action(
|
||||
action,
|
||||
client_id,
|
||||
|
@ -13,6 +13,7 @@ use zellij_utils::data::{
|
||||
};
|
||||
use zellij_utils::errors::prelude::*;
|
||||
use zellij_utils::input::command::RunCommand;
|
||||
use zellij_utils::input::keybinds::Keybinds;
|
||||
use zellij_utils::input::options::Clipboard;
|
||||
use zellij_utils::pane_size::{Size, SizeInPixels};
|
||||
use zellij_utils::{
|
||||
@ -360,6 +361,7 @@ pub enum ScreenInstruction {
|
||||
DumpLayoutToHd,
|
||||
RenameSession(String, ClientId), // String -> new name
|
||||
ListClientsMetadata(Option<PathBuf>, ClientId), // Option<PathBuf> - default shell
|
||||
RebindKeys(Keybinds, ClientId),
|
||||
}
|
||||
|
||||
impl From<&ScreenInstruction> for ScreenContext {
|
||||
@ -544,6 +546,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::DumpLayoutToHd => ScreenContext::DumpLayoutToHd,
|
||||
ScreenInstruction::RenameSession(..) => ScreenContext::RenameSession,
|
||||
ScreenInstruction::ListClientsMetadata(..) => ScreenContext::ListClientsMetadata,
|
||||
ScreenInstruction::RebindKeys(..) => ScreenContext::RebindKeys,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1774,12 +1777,6 @@ impl Screen {
|
||||
tab.mark_active_pane_for_rerender(client_id);
|
||||
tab.update_input_modes()?;
|
||||
}
|
||||
|
||||
if let Some(os_input) = &mut self.bus.os_input {
|
||||
let _ =
|
||||
os_input.send_to_client(client_id, ServerToClientMsg::SwitchToMode(mode_info.mode));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn change_mode_for_all_clients(&mut self, mode_info: ModeInfo) -> Result<()> {
|
||||
@ -2154,6 +2151,23 @@ impl Screen {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn rebind_keys(&mut self, new_keybinds: Keybinds, client_id: ClientId) -> Result<()> {
|
||||
if self.connected_clients_contains(&client_id) {
|
||||
let mode_info = self
|
||||
.mode_info
|
||||
.entry(client_id)
|
||||
.or_insert_with(|| self.default_mode_info.clone());
|
||||
mode_info.update_keybinds(new_keybinds);
|
||||
for tab in self.tabs.values_mut() {
|
||||
tab.change_mode_info(mode_info.clone(), client_id);
|
||||
tab.mark_active_pane_for_rerender(client_id);
|
||||
tab.update_input_modes()?;
|
||||
}
|
||||
} else {
|
||||
log::error!("Could not find client_id {client_id} to rebind keys");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn unblock_input(&self) -> Result<()> {
|
||||
self.bus
|
||||
.senders
|
||||
@ -2295,6 +2309,9 @@ impl Screen {
|
||||
}
|
||||
found_plugin
|
||||
}
|
||||
fn connected_clients_contains(&self, client_id: &ClientId) -> bool {
|
||||
self.connected_clients.borrow().contains(client_id)
|
||||
}
|
||||
}
|
||||
|
||||
// The box is here in order to make the
|
||||
@ -4010,6 +4027,9 @@ pub(crate) fn screen_thread_main(
|
||||
}
|
||||
screen.unblock_input()?;
|
||||
},
|
||||
ScreenInstruction::RebindKeys(new_keybinds, client_id) => {
|
||||
screen.rebind_keys(new_keybinds, client_id).non_fatal();
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -514,6 +514,8 @@ impl MockScreen {
|
||||
background_jobs_thread: None,
|
||||
config_options: Default::default(),
|
||||
layout,
|
||||
client_input_modes: HashMap::new(),
|
||||
client_keybinds: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -571,6 +573,8 @@ impl MockScreen {
|
||||
background_jobs_thread: None,
|
||||
config_options: Default::default(),
|
||||
layout,
|
||||
client_input_modes: HashMap::new(),
|
||||
client_keybinds: HashMap::new(),
|
||||
};
|
||||
|
||||
let os_input = FakeInputOutput::default();
|
||||
@ -2449,31 +2453,41 @@ pub fn send_cli_edit_action_with_split_direction() {
|
||||
|
||||
#[test]
|
||||
pub fn send_cli_switch_mode_action() {
|
||||
let size = Size {
|
||||
cols: 121,
|
||||
rows: 20,
|
||||
};
|
||||
let size = Size { cols: 80, rows: 10 };
|
||||
let client_id = 10; // fake client id should not appear in the screen's state
|
||||
let mut mock_screen = MockScreen::new(size);
|
||||
let session_metadata = mock_screen.clone_session_metadata();
|
||||
let mut initial_layout = TiledPaneLayout::default();
|
||||
initial_layout.children_split_direction = SplitDirection::Vertical;
|
||||
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
|
||||
let mut mock_screen = MockScreen::new(size);
|
||||
let session_metadata = mock_screen.clone_session_metadata();
|
||||
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
|
||||
let received_server_instructions = Arc::new(Mutex::new(vec![]));
|
||||
let server_receiver = mock_screen.server_receiver.take().unwrap();
|
||||
let server_instruction = log_actions_in_thread!(
|
||||
received_server_instructions,
|
||||
ServerInstruction::KillSession,
|
||||
server_receiver
|
||||
);
|
||||
|
||||
let cli_switch_mode = CliAction::SwitchMode {
|
||||
input_mode: InputMode::Locked,
|
||||
};
|
||||
send_cli_action_to_server(&session_metadata, cli_switch_mode, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
|
||||
mock_screen.teardown(vec![screen_thread]);
|
||||
assert_snapshot!(format!(
|
||||
"{:?}",
|
||||
*mock_screen
|
||||
.os_input
|
||||
.server_to_client_messages
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
mock_screen.teardown(vec![server_instruction, screen_thread]);
|
||||
|
||||
let switch_mode_action = received_server_instructions
|
||||
.lock()
|
||||
.unwrap()
|
||||
));
|
||||
.iter()
|
||||
.find(|instruction| match instruction {
|
||||
ServerInstruction::ChangeModeForAllClients(..) => true,
|
||||
_ => false,
|
||||
})
|
||||
.cloned();
|
||||
|
||||
assert_snapshot!(format!("{:?}", switch_mode_action));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
source: zellij-server/src/./unit/screen_tests.rs
|
||||
assertion_line: 2465
|
||||
expression: "format!(\"{:?}\", *\n mock_screen.os_input.server_to_client_messages.lock().unwrap())"
|
||||
assertion_line: 2521
|
||||
expression: "format!(\"{:?}\", switch_mode_action)"
|
||||
---
|
||||
{1: [QueryTerminalSize, SwitchToMode(Locked)]}
|
||||
Some(ChangeModeForAllClients(Locked))
|
||||
|
@ -808,6 +808,14 @@ pub fn dump_session_layout() {
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
/// Rebind keys for the current user
|
||||
pub fn rebind_keys(keys: String) {
|
||||
let plugin_command = PluginCommand::RebindKeys(keys);
|
||||
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||
unsafe { host_run_plugin_command() };
|
||||
}
|
||||
|
||||
// Utility Functions
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -5,7 +5,7 @@ pub struct PluginCommand {
|
||||
pub name: i32,
|
||||
#[prost(
|
||||
oneof = "plugin_command::Payload",
|
||||
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62"
|
||||
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 60, 61, 62, 63"
|
||||
)]
|
||||
pub payload: ::core::option::Option<plugin_command::Payload>,
|
||||
}
|
||||
@ -118,6 +118,8 @@ pub mod plugin_command {
|
||||
ScanHostFolderPayload(::prost::alloc::string::String),
|
||||
#[prost(message, tag = "62")]
|
||||
NewTabsWithLayoutInfoPayload(super::NewTabsWithLayoutInfoPayload),
|
||||
#[prost(string, tag = "63")]
|
||||
RebindKeysPayload(::prost::alloc::string::String),
|
||||
}
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
@ -431,6 +433,7 @@ pub enum CommandName {
|
||||
DumpSessionLayout = 84,
|
||||
CloseSelf = 85,
|
||||
NewTabsWithLayoutInfo = 86,
|
||||
RebindKeys = 87,
|
||||
}
|
||||
impl CommandName {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
@ -526,6 +529,7 @@ impl CommandName {
|
||||
CommandName::DumpSessionLayout => "DumpSessionLayout",
|
||||
CommandName::CloseSelf => "CloseSelf",
|
||||
CommandName::NewTabsWithLayoutInfo => "NewTabsWithLayoutInfo",
|
||||
CommandName::RebindKeys => "RebindKeys",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
@ -618,6 +622,7 @@ impl CommandName {
|
||||
"DumpSessionLayout" => Some(Self::DumpSessionLayout),
|
||||
"CloseSelf" => Some(Self::CloseSelf),
|
||||
"NewTabsWithLayoutInfo" => Some(Self::NewTabsWithLayoutInfo),
|
||||
"RebindKeys" => Some(Self::RebindKeys),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ pub enum PermissionType {
|
||||
WebAccess = 6,
|
||||
ReadCliPipes = 7,
|
||||
MessageAndLaunchOtherPlugins = 8,
|
||||
RebindKeys = 9,
|
||||
}
|
||||
impl PermissionType {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
@ -29,6 +30,7 @@ impl PermissionType {
|
||||
PermissionType::MessageAndLaunchOtherPlugins => {
|
||||
"MessageAndLaunchOtherPlugins"
|
||||
}
|
||||
PermissionType::RebindKeys => "RebindKeys",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
@ -43,6 +45,7 @@ impl PermissionType {
|
||||
"WebAccess" => Some(Self::WebAccess),
|
||||
"ReadCliPipes" => Some(Self::ReadCliPipes),
|
||||
"MessageAndLaunchOtherPlugins" => Some(Self::MessageAndLaunchOtherPlugins),
|
||||
"RebindKeys" => Some(Self::RebindKeys),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::input::actions::Action;
|
||||
use crate::input::config::ConversionError;
|
||||
use crate::input::keybinds::Keybinds;
|
||||
use crate::input::layout::SplitSize;
|
||||
use clap::ArgEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -914,6 +915,7 @@ pub enum Permission {
|
||||
WebAccess,
|
||||
ReadCliPipes,
|
||||
MessageAndLaunchOtherPlugins,
|
||||
RebindKeys,
|
||||
}
|
||||
|
||||
impl PermissionType {
|
||||
@ -934,6 +936,7 @@ impl PermissionType {
|
||||
PermissionType::MessageAndLaunchOtherPlugins => {
|
||||
"Send messages to and launch other plugins".to_owned()
|
||||
},
|
||||
PermissionType::RebindKeys => "Rebind keys".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1130,6 +1133,9 @@ impl ModeInfo {
|
||||
}
|
||||
vec![]
|
||||
}
|
||||
pub fn update_keybinds(&mut self, keybinds: Keybinds) {
|
||||
self.keybinds = keybinds.to_keybinds_vec();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
@ -1733,4 +1739,5 @@ pub enum PluginCommand {
|
||||
DumpSessionLayout,
|
||||
CloseSelf,
|
||||
NewTabsWithLayoutInfo(LayoutInfo),
|
||||
RebindKeys(String), // String -> stringified keybindings
|
||||
}
|
||||
|
@ -353,6 +353,7 @@ pub enum ScreenContext {
|
||||
RenameSession,
|
||||
DumpLayoutToPlugin,
|
||||
ListClientsMetadata,
|
||||
RebindKeys,
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
|
||||
@ -454,6 +455,9 @@ pub enum ServerContext {
|
||||
CliPipeOutput,
|
||||
AssociatePipeWithClient,
|
||||
DisconnectAllClientsExcept,
|
||||
ChangeMode,
|
||||
ChangeModeForAllClients,
|
||||
RebindKeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! IPC stuff for starting to split things into a client and server model.
|
||||
use crate::{
|
||||
cli::CliArgs,
|
||||
data::{ClientId, ConnectToSession, InputMode, Style},
|
||||
data::{ClientId, ConnectToSession, KeyWithModifier, Style},
|
||||
errors::{get_current_ctx, prelude::*, ErrorContext},
|
||||
input::keybinds::Keybinds,
|
||||
input::{actions::Action, layout::Layout, options::Options, plugins::PluginAliases},
|
||||
@ -85,6 +85,7 @@ pub enum ClientToServerMsg {
|
||||
Option<(u32, bool)>, // (pane_id, is_plugin) => pane id to focus
|
||||
),
|
||||
Action(Action, Option<u32>, Option<ClientId>), // u32 is the terminal id
|
||||
Key(KeyWithModifier, Vec<u8>, bool), // key, raw_bytes, is_kitty_keyboard_protocol
|
||||
ClientExited,
|
||||
KillSession,
|
||||
ConnStatus,
|
||||
@ -97,7 +98,6 @@ pub enum ServerToClientMsg {
|
||||
Render(String),
|
||||
UnblockInputThread,
|
||||
Exit(ExitReason),
|
||||
SwitchToMode(InputMode),
|
||||
Connected,
|
||||
ActiveClients(Vec<ClientId>),
|
||||
Log(Vec<String>),
|
||||
|
@ -1880,6 +1880,22 @@ impl Keybinds {
|
||||
}
|
||||
Ok(input_mode_keybinds)
|
||||
}
|
||||
pub fn from_string(
|
||||
stringified_keybindings: String,
|
||||
base_keybinds: Keybinds,
|
||||
config_options: &Options,
|
||||
) -> Result<Self, ConfigError> {
|
||||
let document: KdlDocument = stringified_keybindings.parse()?;
|
||||
if let Some(kdl_keybinds) = document.get("keybinds") {
|
||||
Keybinds::from_kdl(&kdl_keybinds, base_keybinds, config_options)
|
||||
} else {
|
||||
Err(ConfigError::new_kdl_error(
|
||||
format!("Could not find keybinds node"),
|
||||
document.span().offset(),
|
||||
document.span().len(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -98,6 +98,7 @@ enum CommandName {
|
||||
DumpSessionLayout = 84;
|
||||
CloseSelf = 85;
|
||||
NewTabsWithLayoutInfo = 86;
|
||||
RebindKeys = 87;
|
||||
}
|
||||
|
||||
message PluginCommand {
|
||||
@ -155,6 +156,7 @@ message PluginCommand {
|
||||
KillSessionsPayload kill_sessions_payload = 60;
|
||||
string scan_host_folder_payload = 61;
|
||||
NewTabsWithLayoutInfoPayload new_tabs_with_layout_info_payload = 62;
|
||||
string rebind_keys_payload = 63;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,6 +888,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
|
||||
},
|
||||
_ => Err("Mismatched payload for NewTabsWithLayoutInfo"),
|
||||
},
|
||||
Some(CommandName::RebindKeys) => match protobuf_plugin_command.payload {
|
||||
Some(Payload::RebindKeysPayload(rebind_keys_payload)) => {
|
||||
Ok(PluginCommand::RebindKeys(rebind_keys_payload))
|
||||
},
|
||||
_ => Err("Mismatched payload for RebindKeys"),
|
||||
},
|
||||
None => Err("Unrecognized plugin command"),
|
||||
}
|
||||
}
|
||||
@ -1420,6 +1426,10 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
|
||||
)),
|
||||
})
|
||||
},
|
||||
PluginCommand::RebindKeys(rebind_keys_payload) => Ok(ProtobufPluginCommand {
|
||||
name: CommandName::RebindKeys as i32,
|
||||
payload: Some(Payload::RebindKeysPayload(rebind_keys_payload)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,4 +12,5 @@ enum PermissionType {
|
||||
WebAccess = 6;
|
||||
ReadCliPipes = 7;
|
||||
MessageAndLaunchOtherPlugins = 8;
|
||||
RebindKeys = 9;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ impl TryFrom<ProtobufPermissionType> for PermissionType {
|
||||
ProtobufPermissionType::MessageAndLaunchOtherPlugins => {
|
||||
Ok(PermissionType::MessageAndLaunchOtherPlugins)
|
||||
},
|
||||
ProtobufPermissionType::RebindKeys => Ok(PermissionType::RebindKeys),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,6 +50,7 @@ impl TryFrom<PermissionType> for ProtobufPermissionType {
|
||||
PermissionType::MessageAndLaunchOtherPlugins => {
|
||||
Ok(ProtobufPermissionType::MessageAndLaunchOtherPlugins)
|
||||
},
|
||||
PermissionType::RebindKeys => Ok(ProtobufPermissionType::RebindKeys),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user