1
1
mirror of https://github.com/wez/wezterm.git synced 2024-10-26 15:52:29 +03:00

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.
This commit is contained in:
Bogdan-Cristian Tătăroiu 2024-02-13 10:23:31 +00:00
parent b8f94c474c
commit 0f365952a1
15 changed files with 305 additions and 47 deletions

1
Cargo.lock generated
View File

@ -3214,6 +3214,7 @@ dependencies = [
"mux", "mux",
"parking_lot 0.12.2", "parking_lot 0.12.2",
"portable-pty", "portable-pty",
"promise",
"smol", "smol",
"termwiz", "termwiz",
"termwiz-funcs", "termwiz-funcs",

View File

@ -12,7 +12,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))] #![cfg_attr(feature = "cargo-clippy", allow(clippy::range_plus_one))]
use anyhow::{bail, Context as _, Error}; use anyhow::{bail, Context as _, Error};
use config::keyassignment::{PaneDirection, ScrollbackEraseMode}; use config::keyassignment::{PaneDirection, RotationDirection, ScrollbackEraseMode};
use mux::client::{ClientId, ClientInfo}; use mux::client::{ClientId, ClientInfo};
use mux::pane::PaneId; use mux::pane::PaneId;
use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::renderable::{RenderableDimensions, StableCursorPosition};
@ -493,7 +493,7 @@ pdu! {
GetPaneRenderableDimensions: 51, GetPaneRenderableDimensions: 51,
GetPaneRenderableDimensionsResponse: 52, GetPaneRenderableDimensionsResponse: 52,
PaneFocused: 53, PaneFocused: 53,
TabResized: 54, TabReflowed: 54,
TabAddedToWindow: 55, TabAddedToWindow: 55,
TabTitleChanged: 56, TabTitleChanged: 56,
WindowTitleChanged: 57, WindowTitleChanged: 57,
@ -502,6 +502,8 @@ pdu! {
GetPaneDirection: 60, GetPaneDirection: 60,
GetPaneDirectionResponse: 61, GetPaneDirectionResponse: 61,
AdjustPaneSize: 62, AdjustPaneSize: 62,
RotatePanes: 63,
SwapActivePaneWithIndex: 64,
} }
impl Pdu { impl Pdu {
@ -803,7 +805,7 @@ pub struct TabAddedToWindow {
} }
#[derive(Deserialize, Serialize, PartialEq, Debug)] #[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct TabResized { pub struct TabReflowed {
pub tab_id: TabId, pub tab_id: TabId,
} }
@ -887,6 +889,19 @@ pub struct ActivatePaneDirection {
pub direction: PaneDirection, 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)] #[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetPaneRenderChanges { pub struct GetPaneRenderChanges {
pub pane_id: PaneId, pub pane_id: PaneId,

View File

@ -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 { pub enum RotationDirection {
Clockwise, Clockwise,
CounterClockwise, CounterClockwise,

View File

@ -16,6 +16,7 @@ log = "0.4"
luahelper = { path = "../../luahelper" } luahelper = { path = "../../luahelper" }
parking_lot = "0.12" parking_lot = "0.12"
portable-pty = { path = "../../pty" } portable-pty = { path = "../../pty" }
promise = { path = "../../promise" }
smol = "2.0" smol = "2.0"
termwiz = { path = "../../termwiz" } termwiz = { path = "../../termwiz" }
termwiz-funcs = { path = "../termwiz-funcs" } termwiz-funcs = { path = "../termwiz-funcs" }

View File

@ -1,4 +1,4 @@
use config::keyassignment::PaneDirection; use config::keyassignment::{PaneDirection, RotationDirection};
use super::*; use super::*;
use luahelper::mlua::Value; use luahelper::mlua::Value;
@ -113,14 +113,34 @@ impl UserData for MuxTab {
methods.add_method("rotate_counter_clockwise", |_, this, _: ()| { methods.add_method("rotate_counter_clockwise", |_, this, _: ()| {
let mux = get_mux()?; let mux = get_mux()?;
let tab = this.resolve(&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(()) Ok(())
}); });
methods.add_method("rotate_clockwise", |_, this, _: ()| { methods.add_method("rotate_clockwise", |_, this, _: ()| {
let mux = get_mux()?; let mux = get_mux()?;
let tab = this.resolve(&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(()) Ok(())
}); });

View File

@ -12,7 +12,7 @@ use crate::window::WindowId;
use crate::Mux; use crate::Mux;
use anyhow::{bail, Context, Error}; use anyhow::{bail, Context, Error};
use async_trait::async_trait; use async_trait::async_trait;
use config::keyassignment::{SpawnCommand, SpawnTabDomain}; use config::keyassignment::{RotationDirection, SpawnCommand, SpawnTabDomain};
use config::{configuration, ExecDomain, SerialDomain, ValueOrFunc, WslDomain}; use config::{configuration, ExecDomain, SerialDomain, ValueOrFunc, WslDomain};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use parking_lot::Mutex; 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. /// 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 /// If this method returns Ok(None), then the mux will handle the
/// movement itself by mutating its local Tabs and Windows. /// 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, &self,
_pane_id: PaneId, _pane_id: PaneId,
_window_id: Option<WindowId>, _window_id: Option<WindowId>,
@ -151,6 +151,31 @@ pub trait Domain: Downcast + Send + Sync {
Ok(None) 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<bool> {
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<bool> {
Ok(false)
}
/// Returns false if the `spawn` method will never succeed. /// Returns false if the `spawn` method will never succeed.
/// There are some internal placeholder domains that are /// There are some internal placeholder domains that are
/// pre-created with local UI that we do not want to allow /// pre-created with local UI that we do not want to allow

View File

@ -4,7 +4,7 @@ use crate::ssh_agent::AgentProxy;
use crate::tab::{SplitRequest, Tab, TabId}; use crate::tab::{SplitRequest, Tab, TabId};
use crate::window::{Window, WindowId}; use crate::window::{Window, WindowId};
use anyhow::{anyhow, Context, Error}; use anyhow::{anyhow, Context, Error};
use config::keyassignment::SpawnTabDomain; use config::keyassignment::{RotationDirection, SpawnTabDomain};
use config::{configuration, ExitBehavior, GuiPosition}; use config::{configuration, ExitBehavior, GuiPosition};
use domain::{Domain, DomainId, DomainState, SplitSource}; use domain::{Domain, DomainId, DomainState, SplitSource};
use filedescriptor::{poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN}; use filedescriptor::{poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN};
@ -80,7 +80,7 @@ pub enum MuxNotification {
window_id: WindowId, window_id: WindowId,
}, },
PaneFocused(PaneId), PaneFocused(PaneId),
TabResized(TabId), TabReflowed(TabId),
TabTitleChanged { TabTitleChanged {
tab_id: TabId, tab_id: TabId,
title: String, title: String,
@ -1245,7 +1245,7 @@ impl Mux {
.ok_or_else(|| anyhow::anyhow!("domain {domain_id} of pane {pane_id} not found"))?; .ok_or_else(|| anyhow::anyhow!("domain {domain_id} of pane {pane_id} not found"))?;
if let Some((tab, window_id)) = domain 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? .await?
{ {
return Ok((tab, window_id)); return Ok((tab, window_id));
@ -1289,6 +1289,71 @@ impl Mux {
Ok((tab, window_id)) 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( pub async fn spawn_tab_or_window(
&self, &self,
window_id: Option<WindowId>, window_id: Option<WindowId>,

View File

@ -581,12 +581,12 @@ impl Tab {
self.inner.lock().iter_panes_ignoring_zoom() self.inner.lock().iter_panes_ignoring_zoom()
} }
pub fn rotate_counter_clockwise(&self) { pub fn local_rotate_counter_clockwise(&self) {
self.inner.lock().rotate_counter_clockwise() self.inner.lock().local_rotate_counter_clockwise()
} }
pub fn rotate_clockwise(&self) { pub fn local_rotate_clockwise(&self) {
self.inner.lock().rotate_clockwise() self.inner.lock().local_rotate_clockwise()
} }
pub fn iter_splits(&self) -> Vec<PositionedSplit> { pub fn iter_splits(&self) -> Vec<PositionedSplit> {
@ -714,10 +714,10 @@ impl Tab {
} }
/// Swap the active pane with the specified pane_index /// 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 self.inner
.lock() .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 /// Computes the size of the pane that would result if the specified
@ -908,7 +908,7 @@ impl TabInner {
self.zoomed.replace(pane); 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 { fn contains_pane(&self, pane: PaneId) -> bool {
@ -937,7 +937,7 @@ impl TabInner {
self.iter_panes_impl(false) 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(); let panes = self.iter_panes_ignoring_zoom();
if panes.is_empty() { if panes.is_empty() {
// Shouldn't happen, but we check for this here so that the // 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(); let panes = self.iter_panes_ignoring_zoom();
if panes.is_empty() { if panes.is_empty() {
// Shouldn't happen, but we check for this here so that the // 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<PositionedPane> { fn iter_panes_impl(&mut self, respect_zoom_state: bool) -> Vec<PositionedPane> {
@ -1179,7 +1180,7 @@ impl TabInner {
apply_sizes_from_splits(self.pane.as_mut().unwrap(), &size); 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) { fn apply_pane_size(&mut self, pane_size: TerminalSize, cursor: &mut Cursor) {
@ -1255,7 +1256,7 @@ impl TabInner {
self.size = size; 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) { fn resize_split_by(&mut self, split_index: usize, delta: isize) {
@ -1288,7 +1289,7 @@ impl TabInner {
// Now cursor is looking at the split // Now cursor is looking at the split
self.adjust_node_at_cursor(&mut cursor, delta); self.adjust_node_at_cursor(&mut cursor, delta);
self.cascade_size_from_cursor(cursor); 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) { 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) { fn adjust_pane_size(&mut self, direction: PaneDirection, amount: usize) {
@ -1807,7 +1808,7 @@ impl TabInner {
cell_dimensions(&self.size) 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 active_idx = self.get_active_idx();
let mut pane = self.get_active_pane()?; let mut pane = self.get_active_pane()?;
log::trace!( log::trace!(

View File

@ -297,7 +297,7 @@ fn process_unilateral(
.detach(); .detach();
return Ok(()); return Ok(());
} }
Pdu::TabResized(_) | Pdu::TabAddedToWindow(_) => { Pdu::TabReflowed(_) | Pdu::TabAddedToWindow(_) => {
log::trace!("resync due to {:?}", decoded.pdu); log::trace!("resync due to {:?}", decoded.pdu);
promise::spawn::spawn_into_main_thread(async move { promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::try_get().ok_or_else(|| anyhow!("no more mux"))?; let mux = Mux::try_get().ok_or_else(|| anyhow!("no more mux"))?;
@ -1354,6 +1354,12 @@ impl Client {
rpc!(resize, Resize, UnitResponse); rpc!(resize, Resize, UnitResponse);
rpc!(set_zoomed, SetPaneZoomed, UnitResponse); rpc!(set_zoomed, SetPaneZoomed, UnitResponse);
rpc!(activate_pane_direction, ActivatePaneDirection, UnitResponse); rpc!(activate_pane_direction, ActivatePaneDirection, UnitResponse);
rpc!(
swap_active_pane_with_index,
SwapActivePaneWithIndex,
UnitResponse
);
rpc!(rotate_panes, RotatePanes, UnitResponse);
rpc!( rpc!(
get_pane_render_changes, get_pane_render_changes,
GetPaneRenderChanges, GetPaneRenderChanges,

View File

@ -3,7 +3,7 @@ use crate::pane::ClientPane;
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use async_trait::async_trait; use async_trait::async_trait;
use codec::{ListPanesResponse, SpawnV2, SplitPane}; use codec::{ListPanesResponse, SpawnV2, SplitPane};
use config::keyassignment::SpawnTabDomain; use config::keyassignment::{RotationDirection, SpawnTabDomain};
use config::{SshDomain, TlsDomainClient, UnixDomain}; use config::{SshDomain, TlsDomainClient, UnixDomain};
use mux::connui::{ConnectionUI, ConnectionUIParams}; use mux::connui::{ConnectionUI, ConnectionUIParams};
use mux::domain::{alloc_domain_id, Domain, DomainId, DomainState, SplitSource}; 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 /// 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 /// to those that match the remote for the request, resync the changed
/// structure, and then translate the results back to local /// 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, &self,
pane_id: PaneId, pane_id: PaneId,
window_id: Option<WindowId>, window_id: Option<WindowId>,
@ -814,6 +814,64 @@ impl Domain for ClientDomain {
Ok(Some((tab, local_win_id))) Ok(Some((tab, local_win_id)))
} }
async fn remote_rotate_panes(
&self,
pane_id: PaneId,
direction: RotationDirection,
) -> anyhow::Result<bool> {
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::<ClientPane>()
.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<bool> {
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::<ClientPane>()
.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( async fn spawn(
&self, &self,
size: TerminalSize, size: TerminalSize,

View File

@ -88,7 +88,7 @@ impl GuiFrontEnd {
} }
MuxNotification::TabTitleChanged { .. } => {} MuxNotification::TabTitleChanged { .. } => {}
MuxNotification::WindowTitleChanged { .. } => {} MuxNotification::WindowTitleChanged { .. } => {}
MuxNotification::TabResized(_) => {} MuxNotification::TabReflowed(_) => {}
MuxNotification::TabAddedToWindow { .. } => {} MuxNotification::TabAddedToWindow { .. } => {}
MuxNotification::PaneRemoved(_) => {} MuxNotification::PaneRemoved(_) => {}
MuxNotification::WindowInvalidated(_) => {} MuxNotification::WindowInvalidated(_) => {}

View File

@ -30,8 +30,8 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
use ::window::*; use ::window::*;
use anyhow::{anyhow, ensure, Context}; use anyhow::{anyhow, ensure, Context};
use config::keyassignment::{ use config::keyassignment::{
KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments, SpawnCommand,
RotationDirection, SpawnCommand, SplitSize, SplitSize,
}; };
use config::window::WindowLevel; use config::window::WindowLevel;
use config::{ use config::{
@ -1292,7 +1292,7 @@ impl TermWindow {
// Also handled by clientpane // Also handled by clientpane
self.update_title_post_status(); self.update_title_post_status();
} }
MuxNotification::TabResized(_) => { MuxNotification::TabReflowed(_) => {
// Also handled by wezterm-client // Also handled by wezterm-client
self.update_title_post_status(); self.update_title_post_status();
} }
@ -1489,7 +1489,7 @@ impl TermWindow {
return true; return true;
} }
} }
MuxNotification::TabResized(tab_id) MuxNotification::TabReflowed(tab_id)
| MuxNotification::TabTitleChanged { tab_id, .. } => { | MuxNotification::TabTitleChanged { tab_id, .. } => {
let mux = Mux::get(); let mux = Mux::get();
if mux.window_containing_tab(tab_id) == Some(mux_window_id) { if mux.window_containing_tab(tab_id) == Some(mux_window_id) {
@ -3013,10 +3013,15 @@ impl TermWindow {
Some(tab) => tab, Some(tab) => tab,
None => return Ok(PerformAssignmentResult::Handled), None => return Ok(PerformAssignmentResult::Handled),
}; };
match direction { let tab_id = tab.tab_id();
RotationDirection::Clockwise => tab.rotate_clockwise(), let direction = *direction;
RotationDirection::CounterClockwise => tab.rotate_counter_clockwise(), 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) => { SplitPane(split) => {
log::trace!("SplitPane {:?}", split); log::trace!("SplitPane {:?}", split);

View File

@ -180,17 +180,26 @@ impl PaneSelector {
} }
} }
PaneSelectMode::SwapWithActiveKeepFocus | PaneSelectMode::SwapWithActive => { PaneSelectMode::SwapWithActiveKeepFocus | PaneSelectMode::SwapWithActive => {
tab.swap_active_with_index( if let Some(active_pane) = tab.get_active_pane() {
pane_index, let active_pane_id = active_pane.pane_id();
self.mode == PaneSelectMode::SwapWithActiveKeepFocus, 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 => { PaneSelectMode::MoveToNewWindow => {
if let Some(pos) = panes.iter().find(|p| p.index == pane_index) { if let Some(pos) = panes.iter().find(|p| p.index == pane_index) {
let pane_id = pos.pane.pane_id(); let pane_id = pos.pane.pane_id();
promise::spawn::spawn(async move { promise::spawn::spawn(async move {
if let Err(err) = mux.move_pane_to_new_tab(pane_id, None, None).await { 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(); .detach();

View File

@ -172,8 +172,8 @@ where
.await?; .await?;
stream.flush().await.context("flushing PDU to client")?; stream.flush().await.context("flushing PDU to client")?;
} }
Ok(Item::Notif(MuxNotification::TabResized(tab_id))) => { Ok(Item::Notif(MuxNotification::TabReflowed(tab_id))) => {
Pdu::TabResized(codec::TabResized { tab_id }) Pdu::TabReflowed(codec::TabReflowed { tab_id })
.encode_async(&mut stream, 0) .encode_async(&mut stream, 0)
.await?; .await?;
stream.flush().await.context("flushing PDU to client")?; stream.flush().await.context("flushing PDU to client")?;

View File

@ -1,6 +1,7 @@
use crate::PKI; use crate::PKI;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use codec::*; use codec::*;
use config::keyassignment::RotationDirection;
use config::TermConfig; use config::TermConfig;
use mux::client::ClientId; use mux::client::ClientId;
use mux::domain::SplitSource; use mux::domain::SplitSource;
@ -629,6 +630,57 @@ impl SessionHandler {
.detach(); .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 { Pdu::Resize(Resize {
containing_tab_id, containing_tab_id,
pane_id, pane_id,
@ -1004,7 +1056,7 @@ impl SessionHandler {
| Pdu::GetClientListResponse { .. } | Pdu::GetClientListResponse { .. }
| Pdu::PaneRemoved { .. } | Pdu::PaneRemoved { .. }
| Pdu::PaneFocused { .. } | Pdu::PaneFocused { .. }
| Pdu::TabResized { .. } | Pdu::TabReflowed { .. }
| Pdu::GetImageCellResponse { .. } | Pdu::GetImageCellResponse { .. }
| Pdu::MovePaneToNewTabResponse { .. } | Pdu::MovePaneToNewTabResponse { .. }
| Pdu::TabAddedToWindow { .. } | Pdu::TabAddedToWindow { .. }