summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2022-02-21 15:52:42 +0100
committerGitHub <noreply@github.com>2022-02-21 15:52:42 +0100
commita0a0a7e5c4e6fccc755fb5199f7f3b853e7b3746 (patch)
tree8e10d430d12158f14b93272e5182f11231088c36
parent8aef32863f8f21335679273c1a0b186a26482c78 (diff)
feat(ux): tmux mode (#1073)
* work * basic tmux move and functionality * tmux mode ui * rustfmt
-rwxr-xr-xassets/plugins/status-bar.wasmbin648453 -> 546487 bytes
-rwxr-xr-xassets/plugins/strider.wasmbin582179 -> 583654 bytes
-rwxr-xr-xassets/plugins/tab-bar.wasmbin445077 -> 446809 bytes
-rw-r--r--default-plugins/status-bar/src/first_line.rs15
-rw-r--r--default-plugins/status-bar/src/second_line.rs103
-rw-r--r--src/tests/e2e/cases.rs49
-rw-r--r--src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap29
-rw-r--r--zellij-server/src/panes/plugin_pane.rs2
-rw-r--r--zellij-tile/src/data.rs4
-rw-r--r--zellij-utils/assets/config/default.yaml71
-rw-r--r--zellij-utils/src/input/keybinds.rs4
-rw-r--r--zellij-utils/src/input/mod.rs10
12 files changed, 282 insertions, 5 deletions
diff --git a/assets/plugins/status-bar.wasm b/assets/plugins/status-bar.wasm
index 03a0cad8d..53d84a2cd 100755
--- a/assets/plugins/status-bar.wasm
+++ b/assets/plugins/status-bar.wasm
Binary files differ
diff --git a/assets/plugins/strider.wasm b/assets/plugins/strider.wasm
index f06cb8e14..2e7d47d48 100755
--- a/assets/plugins/strider.wasm
+++ b/assets/plugins/strider.wasm
Binary files differ
diff --git a/assets/plugins/tab-bar.wasm b/assets/plugins/tab-bar.wasm
index 2bd618b7c..366a71a19 100755
--- a/assets/plugins/tab-bar.wasm
+++ b/assets/plugins/tab-bar.wasm
Binary files differ
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();