From 7384a8396cf4edba96ffd3d97f09ba794330128d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-Cristian=20T=C4=83t=C4=83roiu?= Date: Tue, 13 Feb 2024 14:42:09 +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_direction.md | 52 ++++++++ .../lua/MuxTab/swap_active_pane_with_index.md | 10 ++ .../keyassignment/SwapActivePaneDirection.md | 45 +++++++ .../keyassignment/SwapActivePaneWithIndex.md | 35 ++++++ lua-api-crates/mux/src/tab.rs | 58 ++++++++- wezterm-gui/src/commands.rs | 112 +++++++++++++++++- wezterm-gui/src/termwindow/mod.rs | 67 ++++++++++- 8 files changed, 390 insertions(+), 5 deletions(-) create mode 100644 docs/config/lua/MuxTab/swap_active_pane_direction.md create mode 100644 docs/config/lua/MuxTab/swap_active_pane_with_index.md create mode 100644 docs/config/lua/keyassignment/SwapActivePaneDirection.md create mode 100644 docs/config/lua/keyassignment/SwapActivePaneWithIndex.md diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index 3bbb494f6bf..81b9c20e396 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 SwapActivePaneWithIndexArguments { + pub pane_index: usize, + pub keep_focus: bool, +} +impl_lua_conversion_dynamic!(SwapActivePaneWithIndexArguments); + #[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), + SwapActivePaneWithIndex(SwapActivePaneWithIndexArguments), TogglePaneZoomState, SetPaneZoomState(bool), CloseCurrentPane { 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/MuxTab/swap_active_pane_with_index.md b/docs/config/lua/MuxTab/swap_active_pane_with_index.md new file mode 100644 index 00000000000..313304c8e92 --- /dev/null +++ b/docs/config/lua/MuxTab/swap_active_pane_with_index.md @@ -0,0 +1,10 @@ +# `tab:swap_active_pane_with_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/keyassignment/SwapActivePaneDirection.md b/docs/config/lua/keyassignment/SwapActivePaneDirection.md new file mode 100644 index 00000000000..8905e7f341d --- /dev/null +++ b/docs/config/lua/keyassignment/SwapActivePaneDirection.md @@ -0,0 +1,45 @@ +# `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 act = wezterm.action +local config = {} + +config.keys = { + { + key = 'LeftArrow', + mods = 'CTRL|ALT', + action = act.SwapActivePaneDirection { direction = 'Prev', keep_focus = true }, + }, + { + key = 'RightArrow', + mods = 'CTRL|ALT', + action = act.SwapActivePaneDirection { direction = 'Next', keep_focus = true }, + }, +} +return config +``` diff --git a/docs/config/lua/keyassignment/SwapActivePaneWithIndex.md b/docs/config/lua/keyassignment/SwapActivePaneWithIndex.md new file mode 100644 index 00000000000..3d3423a71ec --- /dev/null +++ b/docs/config/lua/keyassignment/SwapActivePaneWithIndex.md @@ -0,0 +1,35 @@ +# `SwapActivePaneWithIndex` + +{{since('20220319-142410-0fcdea07')}} + +`SwapActivePaneWithIndex` 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 = act.SwapActivePaneWithIndex { pane_index = 0, keep_focus = true }, + }, + { + key = 'b', + mods = 'CTRL|ALT', + action = act.SwapActivePaneWithIndex { pane_index = 1, keep_focus = true }, + }, + { + key = 'c', + mods = 'CTRL|ALT', + action = act.SwapActivePaneWithIndex { pane_index = 2, keep_focus = true }, + }, +} + +return config +``` diff --git a/lua-api-crates/mux/src/tab.rs b/lua-api-crates/mux/src/tab.rs index 2afde73c97c..1a1313f12af 100644 --- a/lua-api-crates/mux/src/tab.rs +++ b/lua-api-crates/mux/src/tab.rs @@ -1,4 +1,7 @@ -use config::keyassignment::{PaneDirection, RotationDirection}; +use config::keyassignment::{ + PaneDirection, RotationDirection, SwapActivePaneDirectionArguments, + SwapActivePaneWithIndexArguments, +}; use super::*; use luahelper::mlua::Value; @@ -175,5 +178,58 @@ 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(active_pane), Some(with_pane_index)) = ( + tab.get_active_pane(), + tab.get_pane_direction(direction, true), + ) { + let active_pane_id = active_pane.pane_id(); + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux + .swap_active_pane_with_index(active_pane_id, with_pane_index, keep_focus) + .await + { + log::error!("Unable to swap active pane in direction: {:#}", err); + } + }) + .detach(); + } + Ok(()) + }); + + methods.add_method("swap_active_pane_with_index", |_, this, args: Value| { + let mux = get_mux()?; + let tab = this.resolve(&mux)?; + + let SwapActivePaneWithIndexArguments { + pane_index, + keep_focus, + } = from_lua(args)?; + + if let Some(active_pane) = tab.get_active_pane() { + let active_pane_id = active_pane.pane_id(); + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux + .swap_active_pane_with_index(active_pane_id, pane_index, keep_focus) + .await + { + log::error!("Unable to swap active pane in direction: {:#}", err); + } + }) + .detach(); + } + Ok(()) + }); } } diff --git a/wezterm-gui/src/commands.rs b/wezterm-gui/src/commands.rs index 321925e7dfa..15b7440492d 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 715a8e96fcd..7456a0a93b1 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -31,7 +31,7 @@ use ::window::*; use anyhow::{anyhow, ensure, Context}; use config::keyassignment::{ KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, SpawnCommand, - SplitSize, + SplitSize, SwapActivePaneDirectionArguments, SwapActivePaneWithIndexArguments, }; use config::window::WindowLevel; use config::{ @@ -3023,6 +3023,71 @@ impl TermWindow { }) .detach() } + 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(active_pane), Some(with_pane_index)) = ( + tab.get_active_pane(), + tab.get_pane_direction(*direction, true), + ) { + let active_pane_id = active_pane.pane_id(); + let keep_focus = *keep_focus; + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux + .swap_active_pane_with_index( + active_pane_id, + with_pane_index, + keep_focus, + ) + .await + { + log::error!("Unable to swap active pane in direction: {:#}", err); + } + }) + .detach(); + } + } + } + SwapActivePaneWithIndex(SwapActivePaneWithIndexArguments { + 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() { + if let Some(active_pane) = tab.get_active_pane() { + let active_pane_id = active_pane.pane_id(); + let pane_index = *pane_index; + let keep_focus = *keep_focus; + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux + .swap_active_pane_with_index(active_pane_id, pane_index, keep_focus) + .await + { + log::error!("Unable to swap active pane in direction: {:#}", err); + } + }) + .detach(); + } + } + } SplitPane(split) => { log::trace!("SplitPane {:?}", split); self.spawn_command(