feat(ux): tmux mode (#1073)
* work * basic tmux move and functionality * tmux mode ui * rustfmt
This commit is contained in:
parent
8aef32863f
commit
a0a0a7e5c4
12 changed files with 282 additions and 5 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue