From 74621d067d6e4cfaa2f416215dc08c1f90f5eeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-Cristian=20T=C4=83t=C4=83roiu?= Date: Fri, 9 Feb 2024 18:49:20 +0000 Subject: [PATCH] Add support for pane swapping in Key Assignment and Lua API. Adds a couple of variants: - SwapActivePaneDirection / tab:swap_active_pane_direction analogous to ActivatePaneDirection. - SwapActivePaneByIndex / tab:swap_active_pane_by_index analogous to ActivatePaneByIndex. --- config/src/keyassignment.rs | 16 +++ .../lua/MuxTab/swap_active_pane_by_index.md | 10 ++ .../lua/MuxTab/swap_active_pane_direction.md | 52 ++++++++ .../keyassignment/SwapActivePaneByIndex.md | 35 ++++++ .../keyassignment/SwapActivePaneDirection.md | 48 ++++++++ lua-api-crates/mux/src/tab.rs | 30 ++++- wezterm-gui/src/commands.rs | 112 +++++++++++++++++- wezterm-gui/src/termwindow/mod.rs | 37 +++++- 8 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 docs/config/lua/MuxTab/swap_active_pane_by_index.md create mode 100644 docs/config/lua/MuxTab/swap_active_pane_direction.md create mode 100644 docs/config/lua/keyassignment/SwapActivePaneByIndex.md create mode 100644 docs/config/lua/keyassignment/SwapActivePaneDirection.md diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index ed284a5ec98..594a24ce05f 100644 --- a/config/src/keyassignment.rs +++ b/config/src/keyassignment.rs @@ -292,6 +292,20 @@ impl PaneDirection { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] +pub struct SwapActivePaneDirectionArguments { + pub direction: PaneDirection, + pub keep_focus: bool, +} +impl_lua_conversion_dynamic!(SwapActivePaneDirectionArguments); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, FromDynamic, ToDynamic)] +pub struct SwapActivePaneByIndexArguments { + pub pane_index: usize, + pub keep_focus: bool, +} +impl_lua_conversion_dynamic!(SwapActivePaneByIndexArguments); + #[derive(Debug, Copy, Clone, PartialEq, Eq, FromDynamic, ToDynamic, Serialize, Deserialize)] pub enum ScrollbackEraseMode { ScrollbackOnly, @@ -566,6 +580,8 @@ pub enum KeyAssignment { AdjustPaneSize(PaneDirection, usize), ActivatePaneDirection(PaneDirection), ActivatePaneByIndex(usize), + SwapActivePaneDirection(SwapActivePaneDirectionArguments), + SwapActivePaneByIndex(SwapActivePaneByIndexArguments), TogglePaneZoomState, SetPaneZoomState(bool), CloseCurrentPane { diff --git a/docs/config/lua/MuxTab/swap_active_pane_by_index.md b/docs/config/lua/MuxTab/swap_active_pane_by_index.md new file mode 100644 index 00000000000..446c4a19773 --- /dev/null +++ b/docs/config/lua/MuxTab/swap_active_pane_by_index.md @@ -0,0 +1,10 @@ +# `tab:swap_active_pane_by_index{ pane_index, keep_focus }` + +{{since('nightly')}} + +Swaps the active pane with the pane corresponding to *pane_index*. +If *keep_focus* is true, focus is retained on the currently active pane but in +its new position. + +See [tab:panes_with_info](panes_with_info.md) for more information about how to +obtain pane indexes. diff --git a/docs/config/lua/MuxTab/swap_active_pane_direction.md b/docs/config/lua/MuxTab/swap_active_pane_direction.md new file mode 100644 index 00000000000..98003191c7e --- /dev/null +++ b/docs/config/lua/MuxTab/swap_active_pane_direction.md @@ -0,0 +1,52 @@ +# `tab:swap_active_pane_direction{ direction, keep_focus }` + +{{since('nightly')}} + +Swaps the active pane with the pane adjacent to it in the direction *direction*. +If *keep_focus* is true, focus is retained on the currently active pane but in its +new position. + +Valid values for *direction* are: + +* `"Left"` +* `"Right"` +* `"Up"` +* `"Down"` +* `"Prev"` +* `"Next"` + +An example of usage is below: + +```lua +local wezterm = require 'wezterm' +local config = {} + +local function swap_active_pane_action(direction) + return wezterm.action_callback(function(_window, pane) + local tab = pane:tab() + if tab ~= nil then + tab:swap_active_pane_direction { + direction = direction, + keep_focus = true, + } + end + end) +end + +config.keys = { + { + key = 'LeftArrow', + mods = 'CTRL|ALT', + action = swap_active_pane_action 'Prev', + }, + { + key = 'RightArrow', + mods = 'CTRL|ALT', + action = swap_active_pane_action 'Next', + }, +} +return config +``` + +See [ActivatePaneDirection](../keyassignment/ActivatePaneDirection.md) for more information +about how panes are selected given a direction. diff --git a/docs/config/lua/keyassignment/SwapActivePaneByIndex.md b/docs/config/lua/keyassignment/SwapActivePaneByIndex.md new file mode 100644 index 00000000000..4cf236b54aa --- /dev/null +++ b/docs/config/lua/keyassignment/SwapActivePaneByIndex.md @@ -0,0 +1,35 @@ +# `SwapActivePaneByIndex` + +{{since('20220319-142410-0fcdea07')}} + +`SwapActivePaneByIndex` swaps the active pane with the pane with the specified +index within the current tab. Invalid indices are ignored. + +This example causes CTRL-ALT-a, CTRL-ALT-b, CTRL-ALT-c to swap the current pane +with the 0th, 1st and 2nd panes, respectively: + +```lua +local wezterm = require 'wezterm' +local act = wezterm.action +local config = {} + +config.keys = { + { + key = 'a', + mods = 'CTRL|ALT', + action = { SwapActivePaneByIndex = { pane_index = 0, keep_focus = true } }, + }, + { + key = 'b', + mods = 'CTRL|ALT', + action = { SwapActivePaneByIndex = { pane_index = 1, keep_focus = true } }, + }, + { + key = 'c', + mods = 'CTRL|ALT', + action = { SwapActivePaneByIndex = { pane_index = 2, keep_focus = true } }, + }, +} + +return config +``` diff --git a/docs/config/lua/keyassignment/SwapActivePaneDirection.md b/docs/config/lua/keyassignment/SwapActivePaneDirection.md new file mode 100644 index 00000000000..5be3148eb3c --- /dev/null +++ b/docs/config/lua/keyassignment/SwapActivePaneDirection.md @@ -0,0 +1,48 @@ +# `SwapActivePaneDirection` + +{{since('nightly')}} + +`SwapActivePaneDirection` swaps the active pane with the pane adjacent to it in +a specific direction. + +See [ActivatePaneDirection](../keyassignment/ActivatePaneDirection.md) for more information +about how panes are selected given a direction. + +The action requires two named arguments, *direction* and *keep_focus*. + +If *keep_focus* is true, focus is retained on the currently active pane but in its +new position. + +Valid values for *direction* are: + +* `"Left"` +* `"Right"` +* `"Up"` +* `"Down"` +* `"Prev"` +* `"Next"` + +An example of usage is below: + +```lua +local wezterm = require 'wezterm' +local config = {} + +config.keys = { + { + key = 'LeftArrow', + mods = 'CTRL|ALT', + action = { + SwapActivePaneDirection = { direction = 'Prev', keep_focus = true }, + }, + }, + { + key = 'RightArrow', + mods = 'CTRL|ALT', + action = { + SwapActivePaneDirection = { direction = 'Next', keep_focus = true }, + }, + }, +} +return config +``` diff --git a/lua-api-crates/mux/src/tab.rs b/lua-api-crates/mux/src/tab.rs index 0176a05f970..6e39557478f 100644 --- a/lua-api-crates/mux/src/tab.rs +++ b/lua-api-crates/mux/src/tab.rs @@ -1,4 +1,6 @@ -use config::keyassignment::PaneDirection; +use config::keyassignment::{ + PaneDirection, SwapActivePaneByIndexArguments, SwapActivePaneDirectionArguments, +}; use super::*; use luahelper::mlua::Value; @@ -155,5 +157,31 @@ impl UserData for MuxTab { } Ok(()) }); + + methods.add_method("swap_active_pane_direction", |_, this, args: Value| { + let mux = get_mux()?; + let tab = this.resolve(&mux)?; + + let SwapActivePaneDirectionArguments { + direction, + keep_focus, + } = from_lua(args)?; + if let Some(pane_index) = tab.get_pane_direction(direction, true) { + tab.swap_active_with_index(pane_index, keep_focus); + } + Ok(()) + }); + + methods.add_method("swap_active_pane_by_index", |_, this, args: Value| { + let mux = get_mux()?; + let tab = this.resolve(&mux)?; + + let SwapActivePaneByIndexArguments { + pane_index, + keep_focus, + } = from_lua(args)?; + tab.swap_active_with_index(pane_index, keep_focus); + Ok(()) + }); } } diff --git a/wezterm-gui/src/commands.rs b/wezterm-gui/src/commands.rs index 321925e7dfa..d572e0bf0aa 100644 --- a/wezterm-gui/src/commands.rs +++ b/wezterm-gui/src/commands.rs @@ -1015,7 +1015,7 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option { let n = *n; let ordinal = english_ordinal(n as isize); @@ -1023,11 +1023,26 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option { + let ordinal = english_ordinal(*pane_index as isize); + let with_focus = if *keep_focus { ", keeping focus" } else { "" }; + CommandDef { + brief: format!("Swap active pane with {ordinal} pane{with_focus}").into(), + doc: format!("Swaps thhe active pane with the {ordinal} pane").into(), + keys: vec![], + args: &[ArgType::ActiveTab], + menubar: &[], + icon: None, + } + }, SetPaneZoomState(true) => CommandDef { brief: format!("Zooms the current Pane").into(), doc: format!( @@ -1587,6 +1602,65 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option return None, + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction : PaneDirection::Left, keep_focus + }) => CommandDef { + brief: if *keep_focus { + "Swap active pane with left one".into() + } else { + "Swap active pane with left one, keeping focus".into() + }, + doc: "Swaps the current pane with the pane to the left of it".into(), + keys: vec![], + args: &[ArgType::ActivePane], + menubar: &["Window", "Swap Pane"], + icon: None, + }, + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction : PaneDirection::Right, keep_focus + }) => CommandDef { + brief: if *keep_focus { + "Swap active pane with right one".into() + } else { + "Swap active pane with right one, keeping focus".into() + }, + doc: "Swaps the current pane with the pane to the right of it".into(), + keys: vec![], + args: &[ArgType::ActivePane], + menubar: &["Window", "Swap Pane"], + icon: None, + }, + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction : PaneDirection::Up, keep_focus + }) => CommandDef { + brief: if *keep_focus { + "Swap active pane with upwards one".into() + } else { + "Swap active pane with upwards one, keeping focus".into() + }, + doc: "Swaps the current pane with the pane to the top of it".into(), + keys: vec![], + args: &[ArgType::ActivePane], + menubar: &["Window", "Swap Pane"], + icon: None, + }, + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction : PaneDirection::Down, keep_focus + }) => CommandDef { + brief: if *keep_focus { + "Swap active pane with downwards one".into() + } else { + "Swap active pane with downwards one, keeping focus".into() + }, + doc: "Swaps the current pane with the pane to the bottom of it".into(), + keys: vec![], + args: &[ArgType::ActivePane], + menubar: &["Window", "Swap Pane"], + icon: None, + }, TogglePaneZoomState => CommandDef { brief: "Toggle Pane Zoom".into(), doc: "Toggles the zoom state for the current pane".into(), @@ -2129,6 +2203,38 @@ fn compute_default_actions() -> Vec { ActivatePaneDirection(PaneDirection::Right), ActivatePaneDirection(PaneDirection::Up), ActivatePaneDirection(PaneDirection::Down), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Left, + keep_focus: false, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Right, + keep_focus: false, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Up, + keep_focus: false, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Down, + keep_focus: false, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Left, + keep_focus: true, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Right, + keep_focus: true, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Up, + keep_focus: true, + }), + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction: PaneDirection::Down, + keep_focus: true, + }), TogglePaneZoomState, ActivateLastTab, ShowLauncher, diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 870422229d3..614aab61cd8 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -31,7 +31,8 @@ use ::window::*; use anyhow::{anyhow, ensure, Context}; use config::keyassignment::{ KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, - RotationDirection, SpawnCommand, SplitSize, + RotationDirection, SpawnCommand, SplitSize, SwapActivePaneByIndexArguments, + SwapActivePaneDirectionArguments, }; use config::window::WindowLevel; use config::{ @@ -2844,6 +2845,22 @@ impl TermWindow { } } } + SwapActivePaneByIndex(SwapActivePaneByIndexArguments { + pane_index, + keep_focus, + }) => { + let mux = Mux::get(); + let tab = match mux.get_active_tab_for_window(self.mux_window_id) { + Some(tab) => tab, + None => return Ok(PerformAssignmentResult::Handled), + }; + + let tab_id = tab.tab_id(); + + if self.tab_state(tab_id).overlay.is_none() { + tab.swap_active_with_index(*pane_index, *keep_focus); + } + } ActivatePaneDirection(direction) => { let mux = Mux::get(); let tab = match mux.get_active_tab_for_window(self.mux_window_id) { @@ -2857,6 +2874,24 @@ impl TermWindow { tab.activate_pane_direction(*direction); } } + SwapActivePaneDirection(SwapActivePaneDirectionArguments { + direction, + keep_focus, + }) => { + let mux = Mux::get(); + let tab = match mux.get_active_tab_for_window(self.mux_window_id) { + Some(tab) => tab, + None => return Ok(PerformAssignmentResult::Handled), + }; + + let tab_id = tab.tab_id(); + + if self.tab_state(tab_id).overlay.is_none() { + if let Some(pane_index) = tab.get_pane_direction(*direction, true) { + tab.swap_active_with_index(pane_index, *keep_focus); + } + } + } TogglePaneZoomState => { let mux = Mux::get(); let tab = match mux.get_active_tab_for_window(self.mux_window_id) {