diff options
-rwxr-xr-x | assets/plugins/status-bar.wasm | bin | 648453 -> 546487 bytes | |||
-rwxr-xr-x | assets/plugins/strider.wasm | bin | 582179 -> 583654 bytes | |||
-rwxr-xr-x | assets/plugins/tab-bar.wasm | bin | 445077 -> 446809 bytes | |||
-rw-r--r-- | default-plugins/status-bar/src/first_line.rs | 15 | ||||
-rw-r--r-- | default-plugins/status-bar/src/second_line.rs | 103 | ||||
-rw-r--r-- | src/tests/e2e/cases.rs | 49 | ||||
-rw-r--r-- | src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap | 29 | ||||
-rw-r--r-- | zellij-server/src/panes/plugin_pane.rs | 2 | ||||
-rw-r--r-- | zellij-tile/src/data.rs | 4 | ||||
-rw-r--r-- | zellij-utils/assets/config/default.yaml | 71 | ||||
-rw-r--r-- | zellij-utils/src/input/keybinds.rs | 4 | ||||
-rw-r--r-- | zellij-utils/src/input/mod.rs | 10 |
12 files changed, 282 insertions, 5 deletions
diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm Binary files differindex 03a0cad8d..53d84a2cd 100755 --- a/assets/plugins/status-bar.wasm +++ b/assets/plugins/status-bar.wasm diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm Binary files differindex f06cb8e14..2e7d47d48 100755 --- a/assets/plugins/strider.wasm +++ b/assets/plugins/strider.wasm diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm Binary files differindex 2bd618b7c..366a71a19 100755 --- a/assets/plugins/tab-bar.wasm +++ b/assets/plugins/tab-bar.wasm diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index cdb1decee..ed67e5cc5 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -369,5 +369,20 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { colored_elements, separator, ), + InputMode::Tmux => key_indicators( + max_len, + &[ + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Resize), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), + CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit), + ], + colored_elements, + separator, + ), } } diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index abd2f8c3f..d33efc02e 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -208,6 +208,7 @@ fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.palette), InputMode::Locked => locked_interface_indication(help.palette), + InputMode::Tmux => full_tmux_mode_indication(help), InputMode::RenamePane => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help), _ => full_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), } @@ -234,6 +235,7 @@ fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { match help.mode { InputMode::Normal => tip(help.palette), InputMode::Locked => locked_interface_indication(help.palette), + InputMode::Tmux => short_tmux_mode_indication(help), InputMode::RenamePane => { shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help) } @@ -266,6 +268,22 @@ fn best_effort_shortcut_list_nonstandard_mode( } } +fn best_effort_tmux_shortcut_list(help: &ModeInfo, max_len: usize) -> LinePart { + let mut line_part = tmux_mode_indication(help); + for (i, (letter, description)) in help.keybinds.iter().enumerate() { + let shortcut = first_word_shortcut(i == 0, letter, description, help.palette); + if line_part.len + shortcut.len + MORE_MSG.chars().count() > max_len { + // TODO: better + line_part.part = format!("{}{}", line_part.part, MORE_MSG); + line_part.len += MORE_MSG.chars().count(); + break; + } + line_part.len += shortcut.len; + line_part.part = format!("{}{}", line_part.part, shortcut); + } + line_part +} + fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> LinePart { match help.mode { InputMode::Normal => { @@ -284,6 +302,7 @@ fn best_effort_shortcut_list(help: &ModeInfo, tip: TipFn, max_len: usize) -> Lin LinePart::default() } } + InputMode::Tmux => best_effort_tmux_shortcut_list(help, max_len), InputMode::RenamePane => { best_effort_shortcut_list_nonstandard_mode(select_pane_shortcut)(help, max_len) } @@ -377,6 +396,90 @@ pub fn fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> Line } } +pub fn tmux_mode_indication(help: &ModeInfo) -> LinePart { + let white_color = match help.palette.white { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + let orange_color = match help.palette.orange { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + + let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); + let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); + let tmux_mode_text = "TMUX MODE"; + let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); + let line_part = LinePart { + part: format!( + "{}{}{}", + shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator + ), + len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space + }; + line_part +} + +pub fn full_tmux_mode_indication(help: &ModeInfo) -> LinePart { + let white_color = match help.palette.white { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + let orange_color = match help.palette.orange { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + + let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); + let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); + let tmux_mode_text = "TMUX MODE"; + let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); + let mut line_part = LinePart { + part: format!( + "{}{}{}", + shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator + ), + len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space + }; + + for (i, (letter, description)) in help.keybinds.iter().enumerate() { + let shortcut = full_length_shortcut(i == 0, letter, description, help.palette); + line_part.len += shortcut.len; + line_part.part = format!("{}{}", line_part.part, shortcut,); + } + line_part +} + +pub fn short_tmux_mode_indication(help: &ModeInfo) -> LinePart { + let white_color = match help.palette.white { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + let orange_color = match help.palette.orange { + PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), + PaletteColor::EightBit(color) => Fixed(color), + }; + + let shortcut_left_separator = Style::new().fg(white_color).bold().paint(" ("); + let shortcut_right_separator = Style::new().fg(white_color).bold().paint("): "); + let tmux_mode_text = "TMUX MODE"; + let tmux_mode_indicator = Style::new().fg(orange_color).bold().paint(tmux_mode_text); + let mut line_part = LinePart { + part: format!( + "{}{}{}", + shortcut_left_separator, tmux_mode_indicator, shortcut_right_separator + ), + len: tmux_mode_text.chars().count() + 5, // 2 for the separators, 3 for the colon and following space + }; + + for (i, (letter, description)) in help.keybinds.iter().enumerate() { + let shortcut = first_word_shortcut(i == 0, letter, description, help.palette); + line_part.len += shortcut.len; + line_part.part = format!("{}{}", line_part.part, shortcut); + } + line_part +} + pub fn locked_fullscreen_panes_to_hide(palette: &Palette, panes_to_hide: usize) -> LinePart { let white_color = match palette.white { PaletteColor::Rgb((r, g, b)) => RGB(r, g, b), diff --git a/src/tests/e2e/cases.rs b/src/tests/e2e/cases.rs index e2548b52d..d06709517 100644 --- a/src/tests/e2e/cases.rs +++ b/src/tests/e2e/cases.rs @@ -19,10 +19,12 @@ 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 TMUX_MODE: [u8; 1] = [2]; // ctrl-b 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 SPLIT_RIGHT_IN_TMUX_MODE: [u8; 1] = [37]; // % pub const TOGGLE_ACTIVE_TERMINAL_FULLSCREEN_IN_PANE_MODE: [u8; 1] = [102]; // f pub const TOGGLE_FLOATING_PANES: [u8; 1] = [119]; // w pub const CLOSE_PANE_IN_PANE_MODE: [u8; 1] = [120]; // x @@ -1748,3 +1750,50 @@ pub fn focus_tab_with_layout() { }; assert_snapshot!(last_snapshot); } + +#[test] +#[ignore] +pub fn tmux_mode() { + let fake_win_size = Size { + cols: 120, + rows: 24, + }; + + let mut test_attempts = 10; + let last_snapshot = loop { + RemoteRunner::kill_running_sessions(fake_win_size); + let mut runner = RemoteRunner::new(fake_win_size).add_step(Step { + name: "Split pane to the right", + instruction: |mut remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.status_bar_appears() && remote_terminal.cursor_position_is(3, 2) + { + remote_terminal.send_key(&TMUX_MODE); + remote_terminal.send_key(&SPLIT_RIGHT_IN_TMUX_MODE); + // back to normal mode after split + step_is_complete = true; + } + step_is_complete + }, + }); + runner.run_all_steps(); + let last_snapshot = runner.take_snapshot_after(Step { + name: "Wait for new pane to appear", + instruction: |remote_terminal: RemoteTerminal| -> bool { + let mut step_is_complete = false; + if remote_terminal.cursor_position_is(63, 2) && remote_terminal.tip_appears() { + // cursor is in the newly opened second pane + step_is_complete = true; + } + step_is_complete + }, + }); + if runner.test_timed_out && test_attempts > 0 { + test_attempts -= 1; + continue; + } else { + break last_snapshot; + } + }; + assert_snapshot!(last_snapshot); +} diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap new file mode 100644 index 000000000..a9510a196 --- /dev/null +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap @@ -0,0 +1,29 @@ +--- +source: src/tests/e2e/cases.rs +expression: last_snapshot + +--- + Zellij (e2e-test) Tab #1 +┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ +│$ ││$ █ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +│ ││ │ +└──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ + Ctrl + <g> LOCK <p> PANE <t> TAB <n> RESIZE <h> MOVE <s> SCROLL <o> SESSION <q> QUIT + Tip: Alt + <n> => new pane. Alt + <[] or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 4356ec650..b7389bcb7 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -329,7 +329,7 @@ impl Pane for PluginPane { .unwrap(); } fn clear_scroll(&mut self) { - unimplemented!(); + // noop } fn start_selection(&mut self, start: &Position, client_id: ClientId) { self.send_plugin_instructions diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 9b9f67b28..4ef8e493c 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -120,6 +120,9 @@ pub enum InputMode { /// `Prompt` mode allows interacting with active prompts. #[serde(alias = "prompt")] Prompt, + /// `Tmux` mode allows for basic tmux keybindings functionality + #[serde(alias = "tmux")] + Tmux, } impl Default for InputMode { @@ -164,6 +167,7 @@ impl FromStr for InputMode { "renametab" => Ok(InputMode::RenameTab), "session" => Ok(InputMode::Session), "move" => Ok(InputMode::Move), + "tmux" => Ok(InputMode::Tmux), "prompt" => Ok(InputMode::Prompt), "renamepane" => Ok(InputMode::RenamePane), e => Err(e.to_string().into()), diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index 7123e362d..ba3a89163 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -22,6 +22,8 @@ keybinds: key: [Ctrl: 'o',] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [Quit,] key: [Ctrl: 'q',] - action: [NewPane: ] @@ -62,6 +64,8 @@ keybinds: key: [Ctrl: 'o',] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [Quit] key: [Ctrl: 'q'] - action: [Resize: Left,] @@ -113,6 +117,8 @@ keybinds: key: [Ctrl: 'o',] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [Quit,] key: [Ctrl: 'q',] - action: [MoveFocus: Left,] @@ -223,6 +229,8 @@ keybinds: key: [Ctrl: 's'] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [SwitchToMode: Session,] key: [Ctrl: 'o',] - action: [SwitchToMode: RenameTab, TabNameInput: [0],] @@ -290,6 +298,8 @@ keybinds: key: [Ctrl: 'p',] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [SwitchToMode: Session,] key: [Ctrl: 'o',] - action: [SwitchToMode: Resize,] @@ -389,6 +399,8 @@ keybinds: key: [Ctrl: 'p',] - action: [SwitchToMode: Move,] key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] - action: [SwitchToMode: Tab,] key: [Ctrl: 't',] - action: [SwitchToMode: Normal,] @@ -419,6 +431,65 @@ keybinds: key: [ Alt: '+'] - action: [Resize: Decrease,] key: [ Alt: '-'] + tmux: + - action: [SwitchToMode: Locked,] + key: [Ctrl: 'g'] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'n',] + - action: [SwitchToMode: Pane,] + key: [Ctrl: 'p',] + - action: [SwitchToMode: Move,] + key: [Ctrl: 'h',] + - action: [SwitchToMode: Tab,] + key: [Ctrl: 't',] + - action: [SwitchToMode: Normal,] + key: [Ctrl: 'o', Char: "\n", Char: ' ', Esc] + - action: [SwitchToMode: Scroll,] + key: [Ctrl: 's'] + - action: [Quit,] + key: [Ctrl: 'q',] + - action: [NewPane: Down, SwitchToMode: Normal,] + key: [Char: "\"",] + - action: [NewPane: Right, SwitchToMode: Normal,] + key: [Char: '%',] + - action: [ToggleFocusFullscreen, SwitchToMode: Normal,] + key: [Char: 'z',] + - action: [NewTab: , SwitchToMode: Normal,] + key: [ Char: 'c',] + - action: [SwitchToMode: RenameTab, TabNameInput: [0],] + key: [Char: ','] + - action: [GoToPreviousTab, SwitchToMode: Normal,] + key: [ Char: 'p'] + - action: [GoToNextTab, SwitchToMode: Normal,] + key: [ Char: 'n'] + - action: [MoveFocus: Left, SwitchToMode: Normal,] + key: [ Left,] + - action: [MoveFocus: Right, SwitchToMode: Normal,] + key: [ Right,] + - action: [MoveFocus: Down, SwitchToMode: Normal,] + key: [ Down,] + - action: [MoveFocus: Up, SwitchToMode: Normal,] + key: [ Up,] + - action: [NewPane: ,] + key: [ Alt: 'n',] + - action: [MoveFocus: Left,] + key: [ Alt: 'h',] + - action: [MoveFocus: Right,] + key: [ Alt: 'l',] + - action: [MoveFocus: Down,] + key: [ Alt: 'j',] + - action: [MoveFocus: Up,] + key: [ Alt: 'k',] + - action: [FocusPreviousPane,] + key: [ Alt: '[',] + - action: [FocusNextPane,] + key: [ Alt: ']',] + - action: [Resize: Increase,] + key: [ Alt: '='] + - action: [Resize: Increase,] + key: [ Alt: '+'] + - action: [Resize: Decrease,] + key: [ Alt: '-'] plugins: - path: tab-bar tag: tab-bar diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index 5e8ba033e..4838fdf64 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -199,10 +199,6 @@ impl Keybinds { .0 .get(mode) .unwrap_or({ - log::warn!( - "The following mode has no action associated with it: {:?}", - mode - ); // create a dummy mode to recover from &ModeKeybinds::new() }) diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index f8626e04a..391fea7b4 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -60,6 +60,16 @@ pub fn get_mode_info( InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())], InputMode::RenamePane => vec![("Enter".to_string(), "when done".to_string())], InputMode::Session => vec![("d".to_string(), "Detach".to_string())], + InputMode::Tmux => vec![ + ("←↓↑→".to_string(), "Move focus".to_string()), + ("\"".to_string(), "Split Down".to_string()), + ("%".to_string(), "Split Right".to_string()), + ("z".to_string(), "Fullscreen".to_string()), + ("c".to_string(), "New Tab".to_string()), + (",".to_string(), "Rename Tab".to_string()), + ("p".to_string(), "Previous Tab".to_string()), + ("n".to_string(), "Next Tab".to_string()), + ], }; let session_name = envs::get_session_name().ok(); |