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:
parent
b8f94c474c
commit
0f365952a1
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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" }
|
||||||
|
@ -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(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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>,
|
||||||
|
@ -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!(
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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(_) => {}
|
||||||
|
@ -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);
|
||||||
|
@ -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();
|
||||||
|
@ -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")?;
|
||||||
|
@ -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 { .. }
|
||||||
|
Loading…
Reference in New Issue
Block a user