diff --git a/codec/src/lib.rs b/codec/src/lib.rs index 822f5a02c3a..24c82d749df 100644 --- a/codec/src/lib.rs +++ b/codec/src/lib.rs @@ -16,7 +16,7 @@ use config::keyassignment::{PaneDirection, ScrollbackEraseMode}; use mux::client::{ClientId, ClientInfo}; use mux::pane::PaneId; use mux::renderable::{RenderableDimensions, StableCursorPosition}; -use mux::tab::{PaneNode, SerdeUrl, SplitRequest, TabId}; +use mux::tab::{PaneNode, SerdeUrl, SplitRequest, Tab, TabId}; use mux::window::WindowId; use portable_pty::CommandBuilder; use rangeset::*; @@ -503,6 +503,7 @@ pdu! { GetPaneDirectionResponse: 61, AdjustPaneSize: 62, FloatPane: 63, + FloatPaneVisibilityChanged: 64 } impl Pdu { @@ -645,7 +646,7 @@ pub struct ListPanes {} #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct ListPanesResponse { - pub tabs: Vec<(PaneNode, PaneNode)>, + pub tabs: Vec<(PaneNode, (PaneNode, bool))>, pub tab_titles: Vec, pub window_titles: HashMap, } @@ -658,6 +659,12 @@ pub struct FloatPane { pub domain: config::keyassignment::SpawnTabDomain, } +#[derive(Deserialize, Serialize, PartialEq, Debug)] +pub struct FloatPaneVisibilityChanged { + pub tab_id: TabId, + pub visible: bool +} + #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct SplitPane { pub pane_id: PaneId, diff --git a/config/src/keyassignment.rs b/config/src/keyassignment.rs index 939f281fca8..91ca4f29092 100644 --- a/config/src/keyassignment.rs +++ b/config/src/keyassignment.rs @@ -550,6 +550,7 @@ pub enum KeyAssignment { SplitHorizontal(SpawnCommand), SplitVertical(SpawnCommand), FloatPane(SpawnCommand), + ToggleFloatingPane, ShowLauncher, ShowLauncherArgs(LauncherActionArgs), ClearScrollback(ScrollbackEraseMode), diff --git a/mux/src/lib.rs b/mux/src/lib.rs index 75856fb2fd7..9fec7c951f6 100644 --- a/mux/src/lib.rs +++ b/mux/src/lib.rs @@ -93,6 +93,10 @@ pub enum MuxNotification { old_workspace: String, new_workspace: String, }, + FloatPaneVisibilityChanged{ + tab_id: TabId, + visible: bool, + } } static SUB_ID: AtomicUsize = AtomicUsize::new(0); @@ -525,6 +529,16 @@ impl Mux { } } + pub fn set_float_pane_visibility(&self, tab_id: TabId, visible: bool) -> anyhow::Result<()> { + let tab = self + .get_tab(tab_id) + .ok_or_else(|| anyhow::anyhow!("tab {tab_id} not found"))?; + + tab.set_float_pane_visibility(visible); + + Ok(()) + } + /// Called by PaneFocused event handlers to reconcile a remote /// pane focus event and apply its effects locally pub fn focus_pane_and_containing_tab(&self, pane_id: PaneId) -> anyhow::Result<()> { @@ -1056,6 +1070,12 @@ impl Mux { pub fn resolve_pane_id(&self, pane_id: PaneId) -> Option<(DomainId, WindowId, TabId)> { let mut ids = None; for tab in self.tabs.read().values() { + if let Some(float_pane) = tab.get_float_pane() { + if pane_id == float_pane.pane.pane_id() { + ids = Some((tab.tab_id(), float_pane.pane.domain_id())); + break; + } + } for p in tab.iter_panes_ignoring_zoom() { if p.pane.pane_id() == pane_id { ids = Some((tab.tab_id(), p.pane.domain_id())); diff --git a/mux/src/tab.rs b/mux/src/tab.rs index 659316cc9c7..b42ecc5bb0a 100644 --- a/mux/src/tab.rs +++ b/mux/src/tab.rs @@ -46,6 +46,7 @@ struct TabInner { zoomed: Option>, title: String, float_pane: Option>, + float_pane_visible: bool, recency: Recency, } @@ -76,8 +77,7 @@ pub struct PositionedPane { pub height: usize, pub pixel_height: usize, /// The pane instance - pub pane: Arc, - pub is_float: bool, + pub pane: Arc } impl std::fmt::Debug for PositionedPane { @@ -546,14 +546,14 @@ impl Tab { /// PaneEntry, or to create a new Pane from that entry. /// make_pane is expected to add the pane to the mux if it creates /// a new pane, otherwise the pane won't poll/update in the GUI. - pub fn sync_with_pane_tree(&self, size: TerminalSize, root: (PaneNode, PaneNode), make_pane: F) + pub fn sync_with_pane_tree(&self, size: TerminalSize, root: (PaneNode, (PaneNode, bool)), make_pane: F) where F: FnMut(PaneEntry) -> Arc, { self.inner.lock().sync_with_pane_tree(size, root, make_pane) } - pub fn codec_pane_tree(&self) -> (PaneNode, PaneNode) { + pub fn codec_pane_tree(&self) -> (PaneNode, (PaneNode, bool)) { self.inner.lock().codec_pane_tree() } @@ -579,6 +579,10 @@ impl Tab { self.inner.lock().iter_panes() } + pub fn float_pane_is_visible(&self) -> bool { + self.inner.lock().float_pane_is_visible() + } + pub fn get_float_pane(&self) -> Option { self.inner.lock().get_float_pane() } @@ -712,6 +716,10 @@ impl Tab { self.inner.lock().set_active_pane(pane) } + pub fn set_float_pane_visibility(&self, visible: bool) { + self.inner.lock().set_float_pane_visibility(visible); + } + pub fn set_active_idx(&self, pane_index: usize) { self.inner.lock().set_active_idx(pane_index) } @@ -764,6 +772,12 @@ impl Tab { .split_and_insert(pane_index, request, pane) } + pub fn toggle_float(&self) { + self.inner + .lock() + .toggle_float_pane(); + } + pub fn insert_float( &self, float_size: TerminalSize, @@ -791,10 +805,11 @@ impl TabInner { title: String::new(), recency: Recency::default(), float_pane: None, + float_pane_visible: false } } - fn sync_with_pane_tree(&mut self, size: TerminalSize, root: (PaneNode, PaneNode), mut make_pane: F) + fn sync_with_pane_tree(&mut self, size: TerminalSize, root: (PaneNode, (PaneNode, bool)), mut make_pane: F) where F: FnMut(PaneEntry) -> Arc, { @@ -834,9 +849,10 @@ impl TabInner { self.zoomed = zoomed; self.size = size; - if let PaneNode::Leaf(entry) = root.1 { + if let PaneNode::Leaf(entry) = root.1.0 { let float_pane = make_pane(entry); self.float_pane.replace(float_pane); + self.float_pane_visible = root.1.1; } self.resize(size); @@ -850,14 +866,14 @@ impl TabInner { assert!(self.pane.is_some()); } - fn codec_pane_tree(&mut self) -> (PaneNode, PaneNode) { + fn codec_pane_tree(&mut self) -> (PaneNode, (PaneNode, bool)) { let mux = Mux::get(); let tab_id = self.id; let window_id = match mux.window_containing_tab(tab_id) { Some(w) => w, None => { log::error!("no window contains tab {}", tab_id); - return (PaneNode::Empty, PaneNode::Empty); + return (PaneNode::Empty, (PaneNode::Empty, false)); } }; @@ -868,7 +884,7 @@ impl TabInner { Some(ws) => ws, None => { log::error!("window id {} doesn't have a window!?", window_id); - return (PaneNode::Empty, PaneNode::Empty); + return (PaneNode::Empty, (PaneNode::Empty, false)); } }; @@ -881,7 +897,7 @@ impl TabInner { let working_dir = float_pane.get_current_working_dir(CachePolicy::AllowStale); let cursor_pos = float_pane.get_cursor_position(); - PaneNode::Leaf(PaneEntry { + (PaneNode::Leaf(PaneEntry { window_id, tab_id, pane_id: float_pane.pane_id(), @@ -902,9 +918,9 @@ impl TabInner { top_row: 0, left_col: 0, tty_name: float_pane.tty_name(), - }) + }), self.float_pane_visible) } else { - PaneNode::Empty + (PaneNode::Empty, false) }; if let Some(root) = self.pane.as_ref() { @@ -996,6 +1012,10 @@ impl TabInner { self.iter_panes_impl(true) } + fn float_pane_is_visible(&self) -> bool { + self.float_pane_visible + } + fn get_float_pane(&self) -> Option { if let Some(float_pane) = self.float_pane.as_ref() { let root_size = self.size; @@ -1016,8 +1036,7 @@ impl TabInner { height: size.rows, pixel_width: size.pixel_width, pixel_height: size.pixel_height, - pane: Arc::clone(float_pane), - is_float: true + pane: Arc::clone(float_pane) }) } else { None @@ -1109,8 +1128,7 @@ impl TabInner { pixel_width: size.pixel_width.into(), height: size.rows.into(), pixel_height: size.pixel_height.into(), - pane: Arc::clone(zoomed), - is_float: false + pane: Arc::clone(zoomed) }); return panes; } @@ -1156,8 +1174,7 @@ impl TabInner { height: dims.rows as _, pixel_width: dims.pixel_width as _, pixel_height: dims.pixel_height as _, - pane, - is_float: false + pane }); } @@ -1842,7 +1859,7 @@ impl TabInner { } fn is_float_active(&self) -> bool { - return self.float_pane.is_some(); + self.float_pane.is_some() && self.float_pane_visible } fn get_active_pane(&mut self) -> Option> { @@ -1850,8 +1867,10 @@ impl TabInner { return Some(Arc::clone(zoomed)); } - if let Some(float_pane) = self.float_pane.as_ref() { - return Some(Arc::clone(float_pane)); + if self.float_pane_visible { + if let Some(float_pane) = self.float_pane.as_ref() { + return Some(Arc::clone(float_pane)); + } } self.iter_panes_ignoring_zoom() @@ -1884,6 +1903,17 @@ impl TabInner { } } + fn set_float_pane_visibility(&mut self, visible: bool) { + if visible != self.float_pane_visible { + self.float_pane_visible = visible; + let mux = Mux::get(); + mux.notify(MuxNotification::FloatPaneVisibilityChanged{ + tab_id: self.id, + visible, + }); + } + } + fn advise_focus_change(&mut self, prior: Option>) { let mux = Mux::get(); let current = self.get_active_pane(); @@ -2105,6 +2135,16 @@ impl TabInner { }) } + fn toggle_float_pane(&mut self) { + if self.float_pane.is_some() { + if self.float_pane_visible { + self.set_float_pane_visibility(false); + } else { + self.set_float_pane_visibility(true); + } + } + } + fn add_float_pane( &mut self, float_size: TerminalSize, @@ -2119,6 +2159,7 @@ impl TabInner { } self.float_pane = Some(Arc::clone(&pane)); + self.float_pane_visible = true; pane.resize(float_size)?; Ok(()) diff --git a/wezterm-client/src/client.rs b/wezterm-client/src/client.rs index 518bd9e0ce8..5e96978428d 100644 --- a/wezterm-client/src/client.rs +++ b/wezterm-client/src/client.rs @@ -33,6 +33,7 @@ use std::path::{Path, PathBuf}; use std::thread; use std::time::Duration; use thiserror::Error; +use codec::FloatPaneVisibilityChanged; use wezterm_uds::UnixStream; #[derive(Error, Debug)] @@ -297,6 +298,27 @@ fn process_unilateral( .detach(); return Ok(()); } + Pdu::FloatPaneVisibilityChanged(FloatPaneVisibilityChanged { tab_id, visible }) => { + let tab_id = *tab_id; + let visible = visible.clone(); + promise::spawn::spawn_into_main_thread(async move { + let mux = Mux::try_get().ok_or_else(|| anyhow!("no more mux"))?; + let client_domain = mux + .get_domain(local_domain_id) + .ok_or_else(|| anyhow!("no such domain {}", local_domain_id))?; + let client_domain = + client_domain + .downcast_ref::() + .ok_or_else(|| { + anyhow!("domain {} is not a ClientDomain instance", local_domain_id) + })?; + + client_domain.set_float_pane_visibility(tab_id, visible); + anyhow::Result::<()>::Ok(()) + }) + .detach(); + return Ok(()); + } Pdu::TabResized(_) | Pdu::TabAddedToWindow(_) => { log::trace!("resync due to {:?}", decoded.pdu); promise::spawn::spawn_into_main_thread(async move { @@ -1343,6 +1365,7 @@ impl Client { rpc!(spawn_v2, SpawnV2, SpawnResponse); rpc!(split_pane, SplitPane, SpawnResponse); rpc!(add_float_pane, FloatPane, SpawnResponse); + rpc!(set_float_pane_visibility, FloatPaneVisibilityChanged, UnitResponse); rpc!( move_pane_to_new_tab, MovePaneToNewTab, diff --git a/wezterm-client/src/domain.rs b/wezterm-client/src/domain.rs index ff1439ed546..5835956577a 100644 --- a/wezterm-client/src/domain.rs +++ b/wezterm-client/src/domain.rs @@ -15,6 +15,7 @@ use portable_pty::CommandBuilder; use promise::spawn::spawn_into_new_thread; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; +use mux::MuxNotification::FloatPaneVisibilityChanged; use wezterm_term::TerminalSize; pub struct ClientInner { @@ -354,6 +355,22 @@ fn mux_notify_client_domain(local_domain_id: DomainId, notif: MuxNotification) - } } } + MuxNotification::FloatPaneVisibilityChanged { tab_id, visible } => { + if let Some(remote_tab_id) = client_domain.local_to_remote_tab_id(tab_id) { + if let Some(inner) = client_domain.inner() { + promise::spawn::spawn(async move { + inner + .client + .set_float_pane_visibility(codec::FloatPaneVisibilityChanged { + tab_id: remote_tab_id, + visible, + }) + .await + }) + .detach(); + } + } + } MuxNotification::WindowTitleChanged { window_id, title: _, @@ -501,6 +518,16 @@ impl ClientDomain { } } + pub fn set_float_pane_visibility(&self, remote_tab_id: TabId, visible: bool) { + if let Some(inner) = self.inner() { + if let Some(local_tab_id) = inner.remote_to_local_tab_id(remote_tab_id) { + if let Some(tab) = Mux::get().get_tab(local_tab_id) { + tab.set_float_pane_visibility(visible); + } + } + } + } + fn process_pane_list( inner: Arc, panes: ListPanesResponse, diff --git a/wezterm-gui/src/commands.rs b/wezterm-gui/src/commands.rs index 8bdad3b4f04..46d1675daeb 100644 --- a/wezterm-gui/src/commands.rs +++ b/wezterm-gui/src/commands.rs @@ -1621,6 +1621,14 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option CommandDef { + brief: "Toggle floating pane".into(), + doc: "Toggles the visibility state for the current pane".into(), + keys: vec![(Modifiers::CTRL.union(Modifiers::SHIFT), "e".into())], + args: &[ArgType::ActivePane], + menubar: &["Window"], + icon: Some("md_fullscreen"), + }, ActivateLastTab => CommandDef { brief: "Activate the last active tab".into(), doc: "If there was no prior active tab, has no effect.".into(), @@ -2160,6 +2168,7 @@ fn compute_default_actions() -> Vec { ActivatePaneDirection(PaneDirection::Up), ActivatePaneDirection(PaneDirection::Down), TogglePaneZoomState, + ToggleFloatingPane, ActivateLastTab, ShowLauncher, ShowTabNavigator, diff --git a/wezterm-gui/src/frontend.rs b/wezterm-gui/src/frontend.rs index 66c61e72acb..b9bab5d24e7 100644 --- a/wezterm-gui/src/frontend.rs +++ b/wezterm-gui/src/frontend.rs @@ -94,6 +94,7 @@ impl GuiFrontEnd { MuxNotification::WindowInvalidated(_) => {} MuxNotification::PaneOutput(_) => {} MuxNotification::PaneAdded(_) => {} + MuxNotification::FloatPaneVisibilityChanged { .. } => { } MuxNotification::Alert { pane_id, alert: diff --git a/wezterm-gui/src/termwindow/mod.rs b/wezterm-gui/src/termwindow/mod.rs index 80f34b1a5d8..439bc7d761a 100644 --- a/wezterm-gui/src/termwindow/mod.rs +++ b/wezterm-gui/src/termwindow/mod.rs @@ -1289,6 +1289,7 @@ impl TermWindow { // Also handled by clientpane self.update_title_post_status(); } + MuxNotification::FloatPaneVisibilityChanged { .. } => { } MuxNotification::TabResized(_) => { // Also handled by wezterm-client self.update_title_post_status(); @@ -1502,6 +1503,8 @@ impl TermWindow { return true; } } + MuxNotification::FloatPaneVisibilityChanged { .. } => { + } MuxNotification::Alert { alert: Alert::ToastNotification { .. }, .. @@ -2929,6 +2932,14 @@ impl TermWindow { }; tab.toggle_zoom(); } + ToggleFloatingPane => { + 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), + }; + tab.toggle_float(); + } SetPaneZoomState(zoomed) => { let mux = Mux::get(); let tab = match mux.get_active_tab_for_window(self.mux_window_id) { @@ -3453,8 +3464,7 @@ impl TermWindow { height: size.rows as _, pixel_width: size.cols as usize * self.render_metrics.cell_size.width as usize, pixel_height: size.rows as usize * self.render_metrics.cell_size.height as usize, - pane, - is_float: false + pane }] } else { let mut panes = tab.iter_panes(); @@ -3484,6 +3494,10 @@ impl TermWindow { None => return None, }; + if !tab.float_pane_is_visible() { + return None; + } + tab.get_float_pane() } diff --git a/wezterm-gui/src/termwindow/mouseevent.rs b/wezterm-gui/src/termwindow/mouseevent.rs index 92463badab3..bdc4476179c 100644 --- a/wezterm-gui/src/termwindow/mouseevent.rs +++ b/wezterm-gui/src/termwindow/mouseevent.rs @@ -9,7 +9,7 @@ use ::window::{ use config::keyassignment::{KeyAssignment, MouseEventTrigger, SpawnTabDomain}; use config::MouseEventAltScreen; use mux::pane::{Pane, WithPaneLines}; -use mux::tab::SplitDirection; +use mux::tab::{SplitDirection, Tab}; use mux::Mux; use mux_lua::MuxPane; use std::convert::TryInto; @@ -674,6 +674,11 @@ impl super::TermWindow { position.row as usize <= float_pane.top + float_pane.height; if !mouse_in_float { + let mux = Mux::get(); + if let Some(tab) = mux.get_active_tab_for_window(self.mux_window_id){ + mux.set_float_pane_visibility(tab.tab_id(), false).ok(); + } + // Mouse events are not dispatched to the other panes when // a floating pane is active, this is to prevent users from selecting one of the // panes that the float is on top of and encountering some weird behavior, ex. diff --git a/wezterm-mux-server-impl/src/dispatch.rs b/wezterm-mux-server-impl/src/dispatch.rs index c3728f1c2dc..8f981b4605f 100644 --- a/wezterm-mux-server-impl/src/dispatch.rs +++ b/wezterm-mux-server-impl/src/dispatch.rs @@ -3,6 +3,7 @@ use anyhow::Context; use async_ossl::AsyncSslStream; use codec::{DecodedPdu, Pdu}; use futures::FutureExt; +use log::log; use mux::{Mux, MuxNotification}; use smol::prelude::*; use smol::Async; @@ -82,6 +83,7 @@ where return Err(err).context("reading Pdu from client"); } }; + handler.process_one(decoded); } Ok(Item::WritePdu(decoded)) => { @@ -204,6 +206,18 @@ where } Ok(Item::Notif(MuxNotification::ActiveWorkspaceChanged(_))) => {} Ok(Item::Notif(MuxNotification::Empty)) => {} + Ok(Item::Notif(MuxNotification::FloatPaneVisibilityChanged{ + tab_id, + visible + })) => { + Pdu::FloatPaneVisibilityChanged(codec::FloatPaneVisibilityChanged { + tab_id, + visible + }) + .encode_async(&mut stream, 0) + .await?; + stream.flush().await.context("flushing PDU to client")?; + } Err(err) => { log::error!("process_async Err {}", err); return Ok(()); diff --git a/wezterm-mux-server-impl/src/sessionhandler.rs b/wezterm-mux-server-impl/src/sessionhandler.rs index 06f25f3f19e..7336223f789 100644 --- a/wezterm-mux-server-impl/src/sessionhandler.rs +++ b/wezterm-mux-server-impl/src/sessionhandler.rs @@ -732,6 +732,25 @@ impl SessionHandler { .detach(); } + Pdu::FloatPaneVisibilityChanged(FloatPaneVisibilityChanged{ tab_id, visible }) => { + let client_id = self.client_id.clone(); + spawn_into_main_thread(async move { + catch( + move || { + let mux = Mux::get(); + let _identity = mux.with_identity(client_id); + + let tab = mux + .get_tab(tab_id) + .ok_or_else(|| anyhow!("no such tab {}", tab_id))?; + tab.set_float_pane_visibility(visible); + Ok(Pdu::UnitResponse(UnitResponse {})) + }, + send_response + ); + }).detach() + } + Pdu::MovePaneToNewTab(request) => { let client_id = self.client_id.clone(); spawn_into_main_thread(async move { diff --git a/wezterm/src/cli/list.rs b/wezterm/src/cli/list.rs index eec31d948f2..d0b4d825a7d 100644 --- a/wezterm/src/cli/list.rs +++ b/wezterm/src/cli/list.rs @@ -43,7 +43,7 @@ impl ListCommand { } } - if let PaneNode::Leaf(entry) = &tabroot.1 { + if let PaneNode::Leaf(entry) = &tabroot.1.0 { let window_title = panes .window_titles .get(&entry.window_id)