From 0f365952a14feac198c8e0a17807d3e1880d7c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-Cristian=20T=C4=83t=C4=83roiu?= Date: Tue, 13 Feb 2024 10:23:31 +0000 Subject: [PATCH] Fix interaction between pane swapping / rotating and client domains. This should address #4200. Currently pane swapping and rotation only works correctly on local domains since none of the state is propagated to the remote mux server. This patch adds a couple of RPCs for rotating panes and for swapping an active pane with the pane at a given index. Additionally, it renames the corresponding methods on the `mux::tab` module, prefixing them with `local_`, adds `remote_` versions of them to Domains and adds a convenience method on `mux`, mirroring the pattern used for `move_pane_to_new_tab`, which dispatches to the relevant method. This incidentally fixes a typo in the Lua API which was previously always rotating panes in a single direction. --- Cargo.lock | 1 + codec/src/lib.rs | 21 +++++- config/src/keyassignment.rs | 2 +- lua-api-crates/mux/Cargo.toml | 1 + lua-api-crates/mux/src/tab.rs | 26 ++++++- mux/src/domain.rs | 29 +++++++- mux/src/lib.rs | 71 ++++++++++++++++++- mux/src/tab.rs | 31 ++++---- wezterm-client/src/client.rs | 8 ++- wezterm-client/src/domain.rs | 62 +++++++++++++++- wezterm-gui/src/frontend.rs | 2 +- wezterm-gui/src/termwindow/mod.rs | 21 +++--- wezterm-gui/src/termwindow/paneselect.rs | 19 +++-- wezterm-mux-server-impl/src/dispatch.rs | 4 +- wezterm-mux-server-impl/src/sessionhandler.rs | 54 +++++++++++++- 15 files changed, 305 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2122da74988..9905213c449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3214,6 +3214,7 @@ dependencies = [ "mux", "parking_lot 0.12.2", "portable-pty", + "promise", "smol", "termwiz", "termwiz-funcs", diff --git a/codec/src/lib.rs b/codec/src/lib.rs index 1f200d877a5..0c88494902f 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -12,7 +12,7 @@ #![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))] use anyhow::{bail, Context as _, Error}; -use config::keyassignment::{PaneDirection, ScrollbackEraseMode}; +use config::keyassignment::{PaneDirection, RotationDirection, ScrollbackEraseMode}; use mux::client::{ClientId, ClientInfo}; use mux::pane::PaneId; use mux::renderable::{RenderableDimensions, StableCursorPosition}; @@ -493,7 +493,7 @@ pdu! { GetPaneRenderableDimensions: 51, GetPaneRenderableDimensionsResponse: 52, PaneFocused: 53, - TabResized: 54, + TabReflowed: 54, TabAddedToWindow: 55, TabTitleChanged: 56, WindowTitleChanged: 57, @@ -502,6 +502,8 @@ pdu! { GetPaneDirection: 60, GetPaneDirectionResponse: 61, AdjustPaneSize: 62, + RotatePanes: 63, + SwapActivePaneWithIndex: 64, } impl Pdu { @@ -803,7 +805,7 @@ pub struct TabAddedToWindow { } #[derive(Deserialize, Serialize, PartialEq, Debug)] -pub struct TabResized { +pub struct TabReflowed { pub tab_id: TabId, } @@ -887,6 +889,19 @@ pub struct ActivatePaneDirection { pub direction: PaneDirection, } +#[derive(Deserialize, Serialize, PartialEq, Debug)] +pub struct RotatePanes { + pub pane_id: PaneId, + pub direction: RotationDirection, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug)] +pub struct SwapActivePaneWithIndex { + pub active_pane_id: PaneId, + pub with_pane_index: usize, + pub keep_focus: bool, +} + #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct GetPaneRenderChanges { pub pane_id: PaneId, diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index ed284a5ec98..3bbb494f6bf 100644 --- a/config/src/keyassignment.rs +++ b/config/src/keyassignment.rs @@ -641,7 +641,7 @@ impl Default for SplitSize { } } -#[derive(Debug, Clone, PartialEq, Eq, FromDynamic, ToDynamic)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, FromDynamic, ToDynamic)] pub enum RotationDirection { Clockwise, CounterClockwise, diff --git a/lua-api-crates/mux/Cargo.toml b/lua-api-crates/mux/Cargo.toml index 2cdb349d5dc..0d9813c7782 100644 --- a/lua-api-crates/mux/Cargo.toml +++ b/lua-api-crates/mux/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4" luahelper = { path = "../../luahelper" } parking_lot = "0.12" portable-pty = { path = "../../pty" } +promise = { path = "../../promise" } smol = "2.0" termwiz = { path = "../../termwiz" } termwiz-funcs = { path = "../termwiz-funcs" } diff --git a/lua-api-crates/mux/src/tab.rs b/lua-api-crates/mux/src/tab.rs index 0176a05f970..2afde73c97c 100644 --- a/lua-api-crates/mux/src/tab.rs +++ b/lua-api-crates/mux/src/tab.rs @@ -1,4 +1,4 @@ -use config::keyassignment::PaneDirection; +use config::keyassignment::{PaneDirection, RotationDirection}; use super::*; use luahelper::mlua::Value; @@ -113,14 +113,34 @@ impl UserData for MuxTab { methods.add_method("rotate_counter_clockwise", |_, this, _: ()| { let mux = get_mux()?; let tab = this.resolve(&mux)?; - tab.rotate_counter_clockwise(); + + let tab_id = tab.tab_id(); + let direction = RotationDirection::CounterClockwise; + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux.rotate_panes(tab_id, direction).await { + log::error!("Unable to rotate panes: {:#}", err); + } + }) + .detach(); + Ok(()) }); methods.add_method("rotate_clockwise", |_, this, _: ()| { let mux = get_mux()?; let tab = this.resolve(&mux)?; - tab.rotate_counter_clockwise(); + + let tab_id = tab.tab_id(); + let direction = RotationDirection::CounterClockwise; + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux.rotate_panes(tab_id, direction).await { + log::error!("Unable to rotate panes: {:#}", err); + } + }) + .detach(); + Ok(()) }); diff --git a/mux/src/domain.rs b/mux/src/domain.rs index 3cb3767d5c3..4a33eecc79d 100644 --- a/mux/src/domain.rs +++ b/mux/src/domain.rs @@ -12,7 +12,7 @@ use crate::window::WindowId; use crate::Mux; use anyhow::{bail, Context, Error}; use async_trait::async_trait; -use config::keyassignment::{SpawnCommand, SpawnTabDomain}; +use config::keyassignment::{RotationDirection, SpawnCommand, SpawnTabDomain}; use config::{configuration, ExecDomain, SerialDomain, ValueOrFunc, WslDomain}; use downcast_rs::{impl_downcast, Downcast}; use parking_lot::Mutex; @@ -142,7 +142,7 @@ pub trait Domain: Downcast + Send + Sync { /// is being moved to give the domain a chance to handle the movement. /// If this method returns Ok(None), then the mux will handle the /// movement itself by mutating its local Tabs and Windows. - async fn move_pane_to_new_tab( + async fn remote_move_pane_to_new_tab( &self, _pane_id: PaneId, _window_id: Option, @@ -151,6 +151,31 @@ pub trait Domain: Downcast + Send + Sync { Ok(None) } + /// The mux will call this method on the domain of the panes that are being + /// rotated to give the domain a chance to handle the movement. If this + /// method returns Ok(false), then the mux will handle the movement itself + /// by mutating its local Tabs and Windows. + async fn remote_rotate_panes( + &self, + _pane_id: PaneId, + _direction: RotationDirection, + ) -> anyhow::Result { + Ok(false) + } + + /// The mux will call this method on the domain of the pane that is being + /// swapped to give the domain a chance to handle the movement. If this + /// method returns Ok(false), then the mux will handle the movement itself + /// by mutating its local Tabs and Windows. + async fn remote_swap_active_pane_with_index( + &self, + _active_pane_id: PaneId, + _with_pane_index: usize, + _keep_focus: bool, + ) -> anyhow::Result { + Ok(false) + } + /// Returns false if the `spawn` method will never succeed. /// There are some internal placeholder domains that are /// pre-created with local UI that we do not want to allow diff --git a/mux/src/lib.rs b/mux/src/lib.rs index f46e5ee0896..1a3b81de296 100644 --- a/mux/src/lib.rs +++ b/mux/src/lib.rs @@ -4,7 +4,7 @@ use crate::ssh_agent::AgentProxy; use crate::tab::{SplitRequest, Tab, TabId}; use crate::window::{Window, WindowId}; use anyhow::{anyhow, Context, Error}; -use config::keyassignment::SpawnTabDomain; +use config::keyassignment::{RotationDirection, SpawnTabDomain}; use config::{configuration, ExitBehavior, GuiPosition}; use domain::{Domain, DomainId, DomainState, SplitSource}; use filedescriptor::{poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN}; @@ -80,7 +80,7 @@ pub enum MuxNotification { window_id: WindowId, }, PaneFocused(PaneId), - TabResized(TabId), + TabReflowed(TabId), TabTitleChanged { tab_id: TabId, title: String, @@ -1245,7 +1245,7 @@ impl Mux { .ok_or_else(|| anyhow::anyhow!("domain {domain_id} of pane {pane_id} not found"))?; if let Some((tab, window_id)) = domain - .move_pane_to_new_tab(pane_id, window_id, workspace_for_new_window.clone()) + .remote_move_pane_to_new_tab(pane_id, window_id, workspace_for_new_window.clone()) .await? { return Ok((tab, window_id)); @@ -1289,6 +1289,71 @@ impl Mux { Ok((tab, window_id)) } + pub async fn rotate_panes( + &self, + tab_id: TabId, + direction: RotationDirection, + ) -> anyhow::Result<()> { + let tab = match self.get_tab(tab_id) { + Some(tab) => tab, + None => anyhow::bail!("Invalid tab id {}", tab_id), + }; + + // This makes the assumption that a tab contains only panes from a single local domain, + // though that is also an assumption that ClientDomain makes when syncing tab panes. + let tab_panes = tab.iter_panes(); + let pos_pane = match tab_panes.iter().nth(0) { + Some(pos_pane) => pos_pane, + None => anyhow::bail!("Tab contains no panes: {}", tab_id), + }; + + let pane_id = pos_pane.pane.pane_id(); + let domain_id = pos_pane.pane.domain_id(); + + let domain = self + .get_domain(domain_id) + .ok_or_else(|| anyhow::anyhow!("domain {domain_id} of tab {tab_id} not found"))?; + + if domain.remote_rotate_panes(pane_id, direction).await? { + return Ok(()); + } + + match direction { + RotationDirection::Clockwise => tab.local_rotate_clockwise(), + RotationDirection::CounterClockwise => tab.local_rotate_counter_clockwise(), + } + Ok(()) + } + + pub async fn swap_active_pane_with_index( + &self, + active_pane_id: PaneId, + with_pane_index: usize, + keep_focus: bool, + ) -> anyhow::Result<()> { + let (domain_id, _window_id, tab_id) = self + .resolve_pane_id(active_pane_id) + .ok_or_else(|| anyhow::anyhow!("pane {} not found", active_pane_id))?; + + let domain = self.get_domain(domain_id).ok_or_else(|| { + anyhow::anyhow!("domain {domain_id} of pane {active_pane_id} not found") + })?; + + if domain + .remote_swap_active_pane_with_index(active_pane_id, with_pane_index, keep_focus) + .await? + { + return Ok(()); + } + + let tab = match self.get_tab(tab_id) { + Some(tab) => tab, + None => anyhow::bail!("Invalid tab id {}", tab_id), + }; + + tab.local_swap_active_with_index(with_pane_index, keep_focus); + Ok(()) + } pub async fn spawn_tab_or_window( &self, window_id: Option, diff --git a/mux/src/tab.rs b/mux/src/tab.rs index 8eec504fb5f..9989ea129fb 100644 --- a/mux/src/tab.rs +++ b/mux/src/tab.rs @@ -581,12 +581,12 @@ impl Tab { self.inner.lock().iter_panes_ignoring_zoom() } - pub fn rotate_counter_clockwise(&self) { - self.inner.lock().rotate_counter_clockwise() + pub fn local_rotate_counter_clockwise(&self) { + self.inner.lock().local_rotate_counter_clockwise() } - pub fn rotate_clockwise(&self) { - self.inner.lock().rotate_clockwise() + pub fn local_rotate_clockwise(&self) { + self.inner.lock().local_rotate_clockwise() } pub fn iter_splits(&self) -> Vec { @@ -714,10 +714,10 @@ impl Tab { } /// Swap the active pane with the specified pane_index - pub fn swap_active_with_index(&self, pane_index: usize, keep_focus: bool) -> Option<()> { + pub fn local_swap_active_with_index(&self, pane_index: usize, keep_focus: bool) -> Option<()> { self.inner .lock() - .swap_active_with_index(pane_index, keep_focus) + .local_swap_active_with_index(pane_index, keep_focus) } /// Computes the size of the pane that would result if the specified @@ -908,7 +908,7 @@ impl TabInner { self.zoomed.replace(pane); } } - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn contains_pane(&self, pane: PaneId) -> bool { @@ -937,7 +937,7 @@ impl TabInner { self.iter_panes_impl(false) } - fn rotate_counter_clockwise(&mut self) { + fn local_rotate_counter_clockwise(&mut self) { let panes = self.iter_panes_ignoring_zoom(); if panes.is_empty() { // Shouldn't happen, but we check for this here so that the @@ -966,9 +966,10 @@ impl TabInner { } } } + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } - fn rotate_clockwise(&mut self) { + fn local_rotate_clockwise(&mut self) { let panes = self.iter_panes_ignoring_zoom(); if panes.is_empty() { // Shouldn't happen, but we check for this here so that the @@ -997,7 +998,7 @@ impl TabInner { } } } - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn iter_panes_impl(&mut self, respect_zoom_state: bool) -> Vec { @@ -1179,7 +1180,7 @@ impl TabInner { apply_sizes_from_splits(self.pane.as_mut().unwrap(), &size); } - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn apply_pane_size(&mut self, pane_size: TerminalSize, cursor: &mut Cursor) { @@ -1255,7 +1256,7 @@ impl TabInner { self.size = size; } } - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn resize_split_by(&mut self, split_index: usize, delta: isize) { @@ -1288,7 +1289,7 @@ impl TabInner { // Now cursor is looking at the split self.adjust_node_at_cursor(&mut cursor, delta); self.cascade_size_from_cursor(cursor); - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn adjust_node_at_cursor(&mut self, cursor: &mut Cursor, delta: isize) { @@ -1371,7 +1372,7 @@ impl TabInner { } } } - Mux::try_get().map(|mux| mux.notify(MuxNotification::TabResized(self.id))); + Mux::try_get().map(|mux| mux.notify(MuxNotification::TabReflowed(self.id))); } fn adjust_pane_size(&mut self, direction: PaneDirection, amount: usize) { @@ -1807,7 +1808,7 @@ impl TabInner { cell_dimensions(&self.size) } - fn swap_active_with_index(&mut self, pane_index: usize, keep_focus: bool) -> Option<()> { + fn local_swap_active_with_index(&mut self, pane_index: usize, keep_focus: bool) -> Option<()> { let active_idx = self.get_active_idx(); let mut pane = self.get_active_pane()?; log::trace!( diff --git a/wezterm-client/src/client.rs b/wezterm-client/src/client.rs index 0b07d6d95bc..691f129cb7f 100644 --- a/wezterm-client/src/client.rs +++ b/wezterm-client/src/client.rs @@ -297,7 +297,7 @@ fn process_unilateral( .detach(); return Ok(()); } - Pdu::TabResized(_) | Pdu::TabAddedToWindow(_) => { + Pdu::TabReflowed(_) | Pdu::TabAddedToWindow(_) => { log::trace!("resync due to {:?}", decoded.pdu); promise::spawn::spawn_into_main_thread(async move { let mux = Mux::try_get().ok_or_else(|| anyhow!("no more mux"))?; @@ -1354,6 +1354,12 @@ impl Client { rpc!(resize, Resize, UnitResponse); rpc!(set_zoomed, SetPaneZoomed, UnitResponse); rpc!(activate_pane_direction, ActivatePaneDirection, UnitResponse); + rpc!( + swap_active_pane_with_index, + SwapActivePaneWithIndex, + UnitResponse + ); + rpc!(rotate_panes, RotatePanes, UnitResponse); rpc!( get_pane_render_changes, GetPaneRenderChanges, diff --git a/wezterm-client/src/domain.rs b/wezterm-client/src/domain.rs index 7efb1d231c0..b2a7b8ebeb4 100644 --- a/wezterm-client/src/domain.rs +++ b/wezterm-client/src/domain.rs @@ -3,7 +3,7 @@ use crate::pane::ClientPane; use anyhow::{anyhow, bail}; use async_trait::async_trait; use codec::{ListPanesResponse, SpawnV2, SplitPane}; -use config::keyassignment::SpawnTabDomain; +use config::keyassignment::{RotationDirection, SpawnTabDomain}; use config::{SshDomain, TlsDomainClient, UnixDomain}; use mux::connui::{ConnectionUI, ConnectionUIParams}; use mux::domain::{alloc_domain_id, Domain, DomainId, DomainState, SplitSource}; @@ -763,7 +763,7 @@ impl Domain for ClientDomain { /// Forward the request to the remote; we need to translate the local ids /// to those that match the remote for the request, resync the changed /// structure, and then translate the results back to local - async fn move_pane_to_new_tab( + async fn remote_move_pane_to_new_tab( &self, pane_id: PaneId, window_id: Option, @@ -814,6 +814,64 @@ impl Domain for ClientDomain { Ok(Some((tab, local_win_id))) } + async fn remote_rotate_panes( + &self, + pane_id: PaneId, + direction: RotationDirection, + ) -> anyhow::Result { + let inner = self + .inner() + .ok_or_else(|| anyhow!("domain is not attached"))?; + + let local_pane = Mux::get() + .get_pane(pane_id) + .ok_or_else(|| anyhow!("pane_id {} is invalid", pane_id))?; + let pane = local_pane + .downcast_ref::() + .ok_or_else(|| anyhow!("pane_id {} is not a ClientPane", pane_id))?; + + inner + .client + .rotate_panes(codec::RotatePanes { + pane_id: pane.remote_pane_id, + direction, + }) + .await?; + + self.resync().await?; + Ok(true) + } + + async fn remote_swap_active_pane_with_index( + &self, + active_pane_id: PaneId, + with_pane_index: usize, + keep_focus: bool, + ) -> anyhow::Result { + let inner = self + .inner() + .ok_or_else(|| anyhow!("domain is not attached"))?; + + let local_pane = Mux::get() + .get_pane(active_pane_id) + .ok_or_else(|| anyhow!("pane_id {} is invalid", active_pane_id))?; + let pane = local_pane + .downcast_ref::() + .ok_or_else(|| anyhow!("pane_id {} is not a ClientPane", active_pane_id))?; + + inner + .client + .swap_active_pane_with_index(codec::SwapActivePaneWithIndex { + active_pane_id: pane.remote_pane_id, + with_pane_index, + keep_focus, + }) + .await?; + + self.resync().await?; + Ok(true) + } + async fn spawn( &self, size: TerminalSize, diff --git a/wezterm-gui/src/frontend.rs b/wezterm-gui/src/frontend.rs index 66c61e72acb..58b4297f5b9 100644 --- a/wezterm-gui/src/frontend.rs +++ b/wezterm-gui/src/frontend.rs @@ -88,7 +88,7 @@ impl GuiFrontEnd { } MuxNotification::TabTitleChanged { .. } => {} MuxNotification::WindowTitleChanged { .. } => {} - MuxNotification::TabResized(_) => {} + MuxNotification::TabReflowed(_) => {} MuxNotification::TabAddedToWindow { .. } => {} MuxNotification::PaneRemoved(_) => {} MuxNotification::WindowInvalidated(_) => {} diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 71ece24c2df..a983b651164 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -30,8 +30,8 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB}; use ::window::*; use anyhow::{anyhow, ensure, Context}; use config::keyassignment::{ - KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, - RotationDirection, SpawnCommand, SplitSize, + KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, SpawnCommand, + SplitSize, }; use config::window::WindowLevel; use config::{ @@ -1292,7 +1292,7 @@ impl TermWindow { // Also handled by clientpane self.update_title_post_status(); } - MuxNotification::TabResized(_) => { + MuxNotification::TabReflowed(_) => { // Also handled by wezterm-client self.update_title_post_status(); } @@ -1489,7 +1489,7 @@ impl TermWindow { return true; } } - MuxNotification::TabResized(tab_id) + MuxNotification::TabReflowed(tab_id) | MuxNotification::TabTitleChanged { tab_id, .. } => { let mux = Mux::get(); if mux.window_containing_tab(tab_id) == Some(mux_window_id) { @@ -3013,10 +3013,15 @@ impl TermWindow { Some(tab) => tab, None => return Ok(PerformAssignmentResult::Handled), }; - match direction { - RotationDirection::Clockwise => tab.rotate_clockwise(), - RotationDirection::CounterClockwise => tab.rotate_counter_clockwise(), - } + let tab_id = tab.tab_id(); + let direction = *direction; + promise::spawn::spawn(async move { + let mux = Mux::get(); + if let Err(err) = mux.rotate_panes(tab_id, direction).await { + log::error!("Unable to rotate panes: {:#}", err); + } + }) + .detach() } SplitPane(split) => { log::trace!("SplitPane {:?}", split); diff --git a/wezterm-gui/src/termwindow/paneselect.rs b/wezterm-gui/src/termwindow/paneselect.rs index a0ca05c0cc1..dbf82026c43 100644 --- a/wezterm-gui/src/termwindow/paneselect.rs +++ b/wezterm-gui/src/termwindow/paneselect.rs @@ -180,17 +180,26 @@ impl PaneSelector { } } PaneSelectMode::SwapWithActiveKeepFocus | PaneSelectMode::SwapWithActive => { - tab.swap_active_with_index( - pane_index, - self.mode == PaneSelectMode::SwapWithActiveKeepFocus, - ); + if let Some(active_pane) = tab.get_active_pane() { + let active_pane_id = active_pane.pane_id(); + let keep_focus = self.mode == PaneSelectMode::SwapWithActiveKeepFocus; + promise::spawn::spawn(async move { + if let Err(err) = mux + .swap_active_pane_with_index(active_pane_id, pane_index, keep_focus) + .await + { + log::error!("failed to swap_active_pane_with_index: {err:#}"); + } + }) + .detach(); + } } PaneSelectMode::MoveToNewWindow => { if let Some(pos) = panes.iter().find(|p| p.index == pane_index) { let pane_id = pos.pane.pane_id(); promise::spawn::spawn(async move { if let Err(err) = mux.move_pane_to_new_tab(pane_id, None, None).await { - log::error!("failed to move_pane_to_new_tab: {err:#}"); + log::error!("failed to move_pane_to_new_window: {err:#}"); } }) .detach(); diff --git a/wezterm-mux-server-impl/src/dispatch.rs b/wezterm-mux-server-impl/src/dispatch.rs index c3728f1c2dc..777f588b720 100644 --- a/wezterm-mux-server-impl/src/dispatch.rs +++ b/wezterm-mux-server-impl/src/dispatch.rs @@ -172,8 +172,8 @@ where .await?; stream.flush().await.context("flushing PDU to client")?; } - Ok(Item::Notif(MuxNotification::TabResized(tab_id))) => { - Pdu::TabResized(codec::TabResized { tab_id }) + Ok(Item::Notif(MuxNotification::TabReflowed(tab_id))) => { + Pdu::TabReflowed(codec::TabReflowed { tab_id }) .encode_async(&mut stream, 0) .await?; stream.flush().await.context("flushing PDU to client")?; diff --git a/wezterm-mux-server-impl/src/sessionhandler.rs b/wezterm-mux-server-impl/src/sessionhandler.rs index a8a497cdf32..7f8c5ceb765 100644 --- a/wezterm-mux-server-impl/src/sessionhandler.rs +++ b/wezterm-mux-server-impl/src/sessionhandler.rs @@ -1,6 +1,7 @@ use crate::PKI; use anyhow::{anyhow, Context}; use codec::*; +use config::keyassignment::RotationDirection; use config::TermConfig; use mux::client::ClientId; use mux::domain::SplitSource; @@ -629,6 +630,57 @@ impl SessionHandler { .detach(); } + Pdu::SwapActivePaneWithIndex(SwapActivePaneWithIndex { + active_pane_id, + with_pane_index, + keep_focus, + }) => { + spawn_into_main_thread(async move { + catch( + move || { + let mux = Mux::get(); + let (_domain_id, _window_id, tab_id) = mux + .resolve_pane_id(active_pane_id) + .ok_or_else(|| anyhow!("no such pane {}", active_pane_id))?; + let tab = mux + .get_tab(tab_id) + .ok_or_else(|| anyhow!("no such tab {}", tab_id))?; + tab.local_swap_active_with_index(with_pane_index, keep_focus); + Ok(Pdu::UnitResponse(UnitResponse {})) + }, + send_response, + ) + }) + .detach(); + } + + Pdu::RotatePanes(RotatePanes { pane_id, direction }) => { + spawn_into_main_thread(async move { + catch( + move || { + let mux = Mux::get(); + let (_domain_id, _window_id, tab_id) = mux + .resolve_pane_id(pane_id) + .ok_or_else(|| anyhow!("no such pane {}", pane_id))?; + let tab = mux + .get_tab(tab_id) + .ok_or_else(|| anyhow!("no such tab {}", tab_id))?; + + match direction { + RotationDirection::Clockwise => tab.local_rotate_clockwise(), + RotationDirection::CounterClockwise => { + tab.local_rotate_counter_clockwise() + } + }; + + Ok(Pdu::UnitResponse(UnitResponse {})) + }, + send_response, + ) + }) + .detach(); + } + Pdu::Resize(Resize { containing_tab_id, pane_id, @@ -1004,7 +1056,7 @@ impl SessionHandler { | Pdu::GetClientListResponse { .. } | Pdu::PaneRemoved { .. } | Pdu::PaneFocused { .. } - | Pdu::TabResized { .. } + | Pdu::TabReflowed { .. } | Pdu::GetImageCellResponse { .. } | Pdu::MovePaneToNewTabResponse { .. } | Pdu::TabAddedToWindow { .. }