diff --git a/src/frontend/gui/termwindow.rs b/src/frontend/gui/termwindow.rs index b57be1623..f684b04ad 100644 --- a/src/frontend/gui/termwindow.rs +++ b/src/frontend/gui/termwindow.rs @@ -1863,6 +1863,19 @@ impl TermWindow { tab.adjust_pane_size(*direction, *amount); } } + ActivatePaneDirection(direction) => { + let mux = Mux::get().unwrap(); + let tab = match mux.get_active_tab_for_window(self.mux_window_id) { + Some(tab) => tab, + None => return Ok(()), + }; + + let tab_id = tab.tab_id(); + + if self.tab_state(tab_id).overlay.is_none() { + tab.activate_pane_direction(*direction); + } + } }; Ok(()) } diff --git a/src/keyassignment.rs b/src/keyassignment.rs index e3f8c6df7..42aa17576 100644 --- a/src/keyassignment.rs +++ b/src/keyassignment.rs @@ -120,6 +120,7 @@ pub enum KeyAssignment { CompleteSelectionOrOpenLinkAtMouseCursor, AdjustPaneSize(PaneDirection, usize), + ActivatePaneDirection(PaneDirection), } impl_lua_conversion!(KeyAssignment); @@ -295,6 +296,26 @@ impl InputMap { KeyCode::DownArrow, AdjustPaneSize(PaneDirection::Down, 1) ], + [ + ctrl_shift, + KeyCode::LeftArrow, + ActivatePaneDirection(PaneDirection::Left) + ], + [ + ctrl_shift, + KeyCode::RightArrow, + ActivatePaneDirection(PaneDirection::Right) + ], + [ + ctrl_shift, + KeyCode::UpArrow, + ActivatePaneDirection(PaneDirection::Up) + ], + [ + ctrl_shift, + KeyCode::DownArrow, + ActivatePaneDirection(PaneDirection::Down) + ], ); #[cfg(target_os = "macos")] diff --git a/src/mux/tab.rs b/src/mux/tab.rs index 191144834..34eed9076 100644 --- a/src/mux/tab.rs +++ b/src/mux/tab.rs @@ -6,6 +6,7 @@ use async_trait::async_trait; use bintree::PathBranch; use downcast_rs::{impl_downcast, Downcast}; use portable_pty::PtySize; +use rangeset::range_intersection; use serde::{Deserialize, Serialize}; use std::cell::{RefCell, RefMut}; use std::rc::Rc; @@ -566,6 +567,85 @@ impl Tab { } } + /// Activate an adjacent pane in the specified direction. + /// In cases where there are multiple adjacent panes in the + /// intended direction, we take the pane that has the largest + /// edge intersection. + pub fn activate_pane_direction(&self, direction: PaneDirection) { + let panes = self.iter_panes(); + + let active = match panes.iter().find(|pane| pane.is_active) { + Some(p) => p, + None => { + // No active pane somehow... + self.set_active_idx(0); + return; + } + }; + + let mut best = None; + + /// Compute the edge intersection size between two touching panes + fn compute_score( + active_start: usize, + active_size: usize, + current_start: usize, + current_size: usize, + ) -> usize { + range_intersection( + &(active_start..active_start + active_size), + &(current_start..current_start + current_size), + ) + .unwrap_or(0..0) + .count() + } + + for pane in &panes { + let score = match direction { + PaneDirection::Right => { + if pane.left == active.left + active.width + 1 { + compute_score(active.top, active.height, pane.top, pane.height) + } else { + 0 + } + } + PaneDirection::Left => { + if pane.left + pane.width + 1 == active.left { + compute_score(active.top, active.height, pane.top, pane.height) + } else { + 0 + } + } + PaneDirection::Up => { + if pane.top + pane.height + 1 == active.top { + compute_score(active.left, active.width, pane.left, pane.width) + } else { + 0 + } + } + PaneDirection::Down => { + if active.top + active.height + 1 == pane.top { + compute_score(active.left, active.width, pane.left, pane.width) + } else { + 0 + } + } + }; + + if score > 0 { + let target = match best.take() { + Some((best_score, best_pane)) if best_score > score => (best_score, best_pane), + _ => (score, pane), + }; + best.replace(target); + } + } + + if let Some((_, target)) = best.take() { + self.set_active_idx(target.index); + } + } + pub fn prune_dead_panes(&self) -> bool { let mut dead_panes = vec![];