Feature: Move panes directionally (#762)

* Feature: Move panes directionally

* change keybinds

* Fix active pane after move

* Add a separate 'Move' mode

* Add tests

* Add more tests

* Send resize message to pty

* wrap set_terminal_size_using_fd() in macro

* change keybind for Move mode

* cargo fmt

* fix test

* move render functions from tab.rs to screen.rs

* undo wrong keybinds
This commit is contained in:
Kunal Mohan 2021-10-19 20:20:28 +05:30 committed by GitHub
parent 76a96b538b
commit d90e3d4cac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 546 additions and 12 deletions

View File

@ -23,6 +23,7 @@ enum CtrlKeyAction {
Scroll,
Quit,
Session,
Move,
}
enum CtrlKeyMode {
@ -41,6 +42,7 @@ impl CtrlKeyShortcut {
CtrlKeyAction::Scroll => String::from("SCROLL"),
CtrlKeyAction::Quit => String::from("QUIT"),
CtrlKeyAction::Session => String::from("SESSION"),
CtrlKeyAction::Move => String::from("MOVE"),
}
}
pub fn letter_shortcut(&self) -> char {
@ -52,6 +54,7 @@ impl CtrlKeyShortcut {
CtrlKeyAction::Scroll => 's',
CtrlKeyAction::Quit => 'q',
CtrlKeyAction::Session => 'o',
CtrlKeyAction::Move => 'h',
}
}
}
@ -253,6 +256,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Pane),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Move),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Session),
CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit),
@ -267,6 +271,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab),
CtrlKeyShortcut::new(CtrlKeyMode::Selected, 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),
@ -281,6 +286,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
CtrlKeyShortcut::new(CtrlKeyMode::Selected, 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),
@ -295,6 +301,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Pane),
CtrlKeyShortcut::new(CtrlKeyMode::Selected, 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),
@ -309,6 +316,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
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::Selected, CtrlKeyAction::Scroll),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
@ -316,6 +324,21 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
colored_elements,
separator,
),
InputMode::Move => 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::Selected, CtrlKeyAction::Move),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Scroll),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),
],
colored_elements,
separator,
),
InputMode::Normal => key_indicators(
max_len,
&[
@ -323,6 +346,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
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),
@ -337,6 +361,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart {
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::Selected, CtrlKeyAction::Session),
CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Quit),

View File

@ -116,6 +116,15 @@ fn route_action(
};
session.senders.send_to_screen(screen_instr).unwrap();
}
Action::MovePane(direction) => {
let screen_instr = match direction {
Direction::Left => ScreenInstruction::MovePaneLeft,
Direction::Right => ScreenInstruction::MovePaneRight,
Direction::Up => ScreenInstruction::MovePaneUp,
Direction::Down => ScreenInstruction::MovePaneDown,
};
session.senders.send_to_screen(screen_instr).unwrap();
}
Action::ScrollUp => {
session
.senders

View File

@ -44,6 +44,10 @@ pub(crate) enum ScreenInstruction {
MoveFocusUp,
MoveFocusRight,
MoveFocusRightOrNextTab,
MovePaneUp,
MovePaneDown,
MovePaneRight,
MovePaneLeft,
Exit,
ScrollUp,
ScrollUpAt(Position),
@ -100,6 +104,10 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight,
ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab,
ScreenInstruction::MovePaneDown => ScreenContext::MovePaneDown,
ScreenInstruction::MovePaneUp => ScreenContext::MovePaneUp,
ScreenInstruction::MovePaneRight => ScreenContext::MovePaneRight,
ScreenInstruction::MovePaneLeft => ScreenContext::MovePaneLeft,
ScreenInstruction::Exit => ScreenContext::Exit,
ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
@ -637,6 +645,29 @@ pub(crate) fn screen_thread_main(
screen.render();
}
ScreenInstruction::MovePaneDown => {
screen.get_active_tab_mut().unwrap().move_active_pane_down();
screen.render();
}
ScreenInstruction::MovePaneUp => {
screen.get_active_tab_mut().unwrap().move_active_pane_up();
screen.render();
}
ScreenInstruction::MovePaneRight => {
screen
.get_active_tab_mut()
.unwrap()
.move_active_pane_right();
screen.render();
}
ScreenInstruction::MovePaneLeft => {
screen.get_active_tab_mut().unwrap().move_active_pane_left();
screen.render();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()

View File

@ -275,6 +275,20 @@ pub trait Pane {
fn borderless(&self) -> bool;
}
macro_rules! resize_pty {
($pane:expr, $os_input:expr) => {
if let PaneId::Terminal(ref pid) = $pane.pid() {
// FIXME: This `set_terminal_size_using_fd` call would be best in
// `TerminalPane::reflow_lines`
$os_input.set_terminal_size_using_fd(
*pid,
$pane.get_content_columns() as u16,
$pane.get_content_rows() as u16,
);
}
};
}
impl Tab {
// FIXME: Still too many arguments for clippy to be happy...
#[allow(clippy::too_many_arguments)]
@ -756,7 +770,7 @@ impl Tab {
self.draw_pane_frames = draw_pane_frames;
self.should_clear_display_before_rendering = true;
let viewport = self.viewport;
for (pane_id, pane) in self.panes.iter_mut() {
for pane in self.panes.values_mut() {
if !pane.borderless() {
pane.set_frame(draw_pane_frames);
}
@ -783,15 +797,7 @@ impl Tab {
pane.set_content_offset(Offset::shift(pane_rows_offset, pane_columns_offset));
}
// FIXME: This, and all other `set_terminal_size_using_fd` calls, would be best in
// `TerminalPane::reflow_lines`
if let PaneId::Terminal(pid) = pane_id {
self.os_api.set_terminal_size_using_fd(
*pid,
pane.get_content_columns() as u16,
pane.get_content_rows() as u16,
);
}
resize_pty!(pane, self.os_api);
}
}
pub fn render(&mut self) -> Option<Output> {
@ -2031,6 +2037,170 @@ impl Tab {
self.active_terminal = updated_active_terminal;
false
}
pub fn move_active_pane_down(&mut self) {
if !self.has_selectable_panes() {
return;
}
if self.fullscreen_is_active {
return;
}
if let Some(active) = self.get_active_pane() {
let terminals = self.get_selectable_panes();
let next_index = terminals
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_below(active) && c.vertically_overlaps_with(active)
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid);
if let Some(&p) = next_index {
let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.get_geom_override(geom);
}
resize_pty!(new_position, self.os_api);
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.get_geom_override(geom);
}
resize_pty!(current_position, self.os_api);
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_up(&mut self) {
if !self.has_selectable_panes() {
return;
}
if self.fullscreen_is_active {
return;
}
if let Some(active) = self.get_active_pane() {
let terminals = self.get_selectable_panes();
let next_index = terminals
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_above(active) && c.vertically_overlaps_with(active)
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid);
if let Some(&p) = next_index {
let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.get_geom_override(geom);
}
resize_pty!(new_position, self.os_api);
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.get_geom_override(geom);
}
resize_pty!(current_position, self.os_api);
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_right(&mut self) {
if !self.has_selectable_panes() {
return;
}
if self.fullscreen_is_active {
return;
}
if let Some(active) = self.get_active_pane() {
let terminals = self.get_selectable_panes();
let next_index = terminals
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_right_of(active) && c.horizontally_overlaps_with(active)
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid);
if let Some(&p) = next_index {
let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.get_geom_override(geom);
}
resize_pty!(new_position, self.os_api);
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.get_geom_override(geom);
}
resize_pty!(current_position, self.os_api);
current_position.set_should_render(true);
}
}
}
pub fn move_active_pane_left(&mut self) {
if !self.has_selectable_panes() {
return;
}
if self.fullscreen_is_active {
return;
}
if let Some(active) = self.get_active_pane() {
let terminals = self.get_selectable_panes();
let next_index = terminals
.enumerate()
.filter(|(_, (_, c))| {
c.is_directly_left_of(active) && c.horizontally_overlaps_with(active)
})
.max_by_key(|(_, (_, c))| c.active_at())
.map(|(_, (pid, _))| pid);
if let Some(&p) = next_index {
let current_position = self.panes.get(&self.active_terminal.unwrap()).unwrap();
let prev_geom = current_position.position_and_size();
let prev_geom_override = current_position.geom_override();
let new_position = self.panes.get_mut(&p).unwrap();
let next_geom = new_position.position_and_size();
let next_geom_override = new_position.geom_override();
new_position.set_geom(prev_geom);
if let Some(geom) = prev_geom_override {
new_position.get_geom_override(geom);
}
resize_pty!(new_position, self.os_api);
new_position.set_should_render(true);
let current_position = self.panes.get_mut(&self.active_terminal.unwrap()).unwrap();
current_position.set_geom(next_geom);
if let Some(geom) = next_geom_override {
current_position.get_geom_override(geom);
}
resize_pty!(current_position, self.os_api);
current_position.set_should_render(true);
}
}
}
fn horizontal_borders(&self, terminals: &[PaneId]) -> HashSet<usize> {
terminals.iter().fold(HashSet::new(), |mut borders, t| {
let terminal = self.panes.get(t).unwrap();

View File

@ -2525,6 +2525,244 @@ pub fn move_focus_right_to_the_most_recently_used_pane() {
);
}
#[test]
pub fn move_active_pane_down() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id = PaneId::Terminal(2);
tab.horizontal_split(new_pane_id);
tab.move_focus_up();
tab.move_active_pane_down();
assert_eq!(
tab.get_active_pane().unwrap().y(),
10,
"Active pane is the bottom one"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(1),
"Active pane is the bottom one"
);
}
#[test]
pub fn move_active_pane_down_to_the_most_recently_used_position() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id_1 = PaneId::Terminal(2);
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
tab.horizontal_split(new_pane_id_1);
tab.vertical_split(new_pane_id_2);
tab.vertical_split(new_pane_id_3);
tab.move_focus_up();
tab.move_active_pane_down();
assert_eq!(
tab.get_active_pane().unwrap().y(),
10,
"Active pane y position"
);
assert_eq!(
tab.get_active_pane().unwrap().x(),
91,
"Active pane x position"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(1),
"Active pane PaneId"
);
}
#[test]
pub fn move_active_pane_up() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id = PaneId::Terminal(2);
tab.horizontal_split(new_pane_id);
tab.move_active_pane_up();
assert_eq!(
tab.get_active_pane().unwrap().y(),
0,
"Active pane is the top one"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(2),
"Active pane is the top one"
);
}
#[test]
pub fn move_active_pane_up_to_the_most_recently_used_position() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id_1 = PaneId::Terminal(2);
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
tab.horizontal_split(new_pane_id_1);
tab.move_focus_up();
tab.vertical_split(new_pane_id_2);
tab.vertical_split(new_pane_id_3);
tab.move_focus_down();
tab.move_active_pane_up();
assert_eq!(
tab.get_active_pane().unwrap().y(),
0,
"Active pane y position"
);
assert_eq!(
tab.get_active_pane().unwrap().x(),
91,
"Active pane x position"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(2),
"Active pane PaneId"
);
}
#[test]
pub fn move_active_pane_left() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id = PaneId::Terminal(2);
tab.vertical_split(new_pane_id);
tab.move_active_pane_left();
assert_eq!(
tab.get_active_pane().unwrap().x(),
0,
"Active pane is the left one"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(2),
"Active pane is the left one"
);
}
#[test]
pub fn move_active_pane_left_to_the_most_recently_used_position() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id_1 = PaneId::Terminal(2);
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
tab.vertical_split(new_pane_id_1);
tab.move_focus_left();
tab.horizontal_split(new_pane_id_2);
tab.horizontal_split(new_pane_id_3);
tab.move_focus_right();
tab.move_active_pane_left();
assert_eq!(
tab.get_active_pane().unwrap().y(),
15,
"Active pane y position"
);
assert_eq!(
tab.get_active_pane().unwrap().x(),
0,
"Active pane x position"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(2),
"Active pane PaneId"
);
}
#[test]
pub fn move_active_pane_right() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id = PaneId::Terminal(2);
tab.vertical_split(new_pane_id);
tab.move_focus_left();
tab.move_active_pane_right();
assert_eq!(
tab.get_active_pane().unwrap().x(),
61,
"Active pane is the right one"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(1),
"Active pane is the right one"
);
}
#[test]
pub fn move_active_pane_right_to_the_most_recently_used_position() {
let size = Size {
cols: 121,
rows: 20,
};
let mut tab = create_new_tab(size);
let new_pane_id_1 = PaneId::Terminal(2);
let new_pane_id_2 = PaneId::Terminal(3);
let new_pane_id_3 = PaneId::Terminal(4);
tab.vertical_split(new_pane_id_1);
tab.horizontal_split(new_pane_id_2);
tab.horizontal_split(new_pane_id_3);
tab.move_focus_left();
tab.move_active_pane_right();
assert_eq!(
tab.get_active_pane().unwrap().y(),
15,
"Active pane y position"
);
assert_eq!(
tab.get_active_pane().unwrap().x(),
61,
"Active pane x position"
);
assert_eq!(
tab.get_active_pane().unwrap().pid(),
PaneId::Terminal(1),
"Active pane Paneid"
);
}
#[test]
pub fn resize_down_with_pane_above() {
// ┌───────────┐ ┌───────────┐

View File

@ -81,6 +81,9 @@ pub enum InputMode {
/// `Session` mode allows detaching sessions
#[serde(alias = "session")]
Session,
/// `Move` mode allows moving the different existing panes within a tab
#[serde(alias = "move")]
Move,
}
impl Default for InputMode {
@ -124,6 +127,7 @@ impl FromStr for InputMode {
"scroll" => Ok(InputMode::Scroll),
"renametab" => Ok(InputMode::RenameTab),
"session" => Ok(InputMode::Session),
"move" => Ok(InputMode::Move),
e => Err(e.to_string().into()),
}
}

View File

@ -20,6 +20,8 @@ keybinds:
key: [Ctrl: 's',]
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [Quit,]
key: [Ctrl: 'q',]
- action: [NewPane: ]
@ -52,6 +54,8 @@ keybinds:
key: [Ctrl: 's']
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [Quit]
key: [Ctrl: 'q']
- action: [Resize: Left,]
@ -89,6 +93,8 @@ keybinds:
key: [Ctrl: 's']
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [Quit,]
key: [Ctrl: 'q',]
- action: [MoveFocus: Left,]
@ -117,6 +123,45 @@ keybinds:
key: [ Alt: '[',]
- action: [FocusNextPane,]
key: [ Alt: ']',]
move:
- action: [SwitchToMode: Locked,]
key: [Ctrl: 'g']
- action: [SwitchToMode: Pane,]
key: [Ctrl: 'p',]
- action: [SwitchToMode: Tab,]
key: [Ctrl: 't',]
- action: [SwitchToMode: Resize,]
key: [Ctrl: 'n',]
- action: [SwitchToMode: Normal,]
key: [Ctrl: 'h', Char: "\n", Char: ' ',]
- action: [SwitchToMode: Scroll,]
key: [Ctrl: 's']
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [Quit]
key: [Ctrl: 'q']
- action: [MovePane: Left,]
key: [Char: 'h', Left,]
- action: [MovePane: Down,]
key: [Char: 'j', Down,]
- action: [MovePane: Up,]
key: [Char: 'k', Up, ]
- action: [MovePane: Right,]
key: [Char: 'l', Right,]
- 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: ']',]
tab:
- action: [SwitchToMode: Locked,]
key: [Ctrl: 'g']
@ -128,6 +173,8 @@ keybinds:
key: [Ctrl: 't', Char: "\n", Char: ' ',]
- action: [SwitchToMode: Scroll,]
key: [Ctrl: 's']
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [SwitchToMode: RenameTab, TabNameInput: [0],]
@ -186,6 +233,8 @@ keybinds:
key: [Ctrl: 'g',]
- action: [SwitchToMode: Pane,]
key: [Ctrl: 'p',]
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [SwitchToMode: Session,]
key: [Ctrl: 'o',]
- action: [SwitchToMode: Resize,]
@ -244,6 +293,8 @@ keybinds:
key: [Ctrl: 'n',]
- action: [SwitchToMode: Pane,]
key: [Ctrl: 'p',]
- action: [SwitchToMode: Move,]
key: [Ctrl: 'h',]
- action: [SwitchToMode: Tab,]
key: [Ctrl: 't',]
- action: [SwitchToMode: Normal,]

View File

@ -226,6 +226,10 @@ pub enum ScreenContext {
MoveFocusUp,
MoveFocusRight,
MoveFocusRightOrNextTab,
MovePaneDown,
MovePaneUp,
MovePaneRight,
MovePaneLeft,
Exit,
ScrollUp,
ScrollUpAt,

View File

@ -41,6 +41,7 @@ pub enum Action {
/// Tries to move the focus pane in specified direction.
/// If there is no pane in the direction, move to previous/next Tab.
MoveFocusOrTab(Direction),
MovePane(Direction),
/// Scroll up in focus pane.
ScrollUp,
/// Scroll up at point

View File

@ -23,6 +23,7 @@ pub fn get_mode_info(
let keybinds = match mode {
InputMode::Normal | InputMode::Locked => Vec::new(),
InputMode::Resize => vec![("←↓↑→".to_string(), "Resize".to_string())],
InputMode::Move => vec![("←↓↑→".to_string(), "Move".to_string())],
InputMode::Pane => vec![
("←↓↑→".to_string(), "Move focus".to_string()),
("p".to_string(), "Next".to_string()),

View File

@ -378,7 +378,7 @@ fn unbind_multiple_keybinds_all_modes() {
let result_normal_2 = mode_keybinds_normal
.expect("ModeKeybinds shouldn't be empty")
.0
.get(&Key::Ctrl('h'));
.get(&Key::Ctrl('f'));
let result_resize_1 = mode_keybinds_resize
.expect("ModeKeybinds shouldn't be empty")
.0
@ -386,7 +386,7 @@ fn unbind_multiple_keybinds_all_modes() {
let result_resize_2 = mode_keybinds_resize
.expect("ModeKeybinds shouldn't be empty")
.0
.get(&Key::Ctrl('h'));
.get(&Key::Ctrl('f'));
assert!(result_normal_1.is_none());
assert!(result_resize_1.is_none());
assert!(result_normal_2.is_none());