1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

mux: improve Window closing behavior

Finally getting around to fixing this usability wart: this commit
changes the behavior of Window closing so that you can close a window
containing multiplexer panes without prompting and without killing
off those panes.

This is achieved through some plumbing:

* The mux can now advise Domains about an impending window closure,
  giving them the opportunity to "do things" in readiness.
* The mux client domain informs the container ClientPane instances
  to ignore the next Pane::kill call, which would otherwise inform
  the mux server to kill the remote pane
* Pane:can_close_without_prompting now requires a CloseReason.
* ClientPane's can_close_without_prompting impl allows Window closing
  without prompting on the assumption that the ignore-next-kill hack
  above is working

refs: #848
refs: #917
refs: #1224
This commit is contained in:
Wez Furlong 2021-12-19 09:00:58 -07:00
parent 9e9e76bf0a
commit e9f1297b45
11 changed files with 85 additions and 13 deletions

View File

@ -41,6 +41,7 @@ As features stabilize some brief notes about them will accumulate here.
* Changing font scaling on Windows no longer causes the initial terminal rows/cols to be under-sized [#1381](https://github.com/wez/wezterm/issues/1381) * Changing font scaling on Windows no longer causes the initial terminal rows/cols to be under-sized [#1381](https://github.com/wez/wezterm/issues/1381)
* New version update notifications are now more coordinated between multiple wezterm GUI instances, and update related configuration now respects configuration reloading. [#1402](https://github.com/wez/wezterm/issues/1402) * New version update notifications are now more coordinated between multiple wezterm GUI instances, and update related configuration now respects configuration reloading. [#1402](https://github.com/wez/wezterm/issues/1402)
* [TLS domains](multiplexing.md) bootstrapping via SSH now use the `libssh` backend by default and work more reliably on Windows * [TLS domains](multiplexing.md) bootstrapping via SSH now use the `libssh` backend by default and work more reliably on Windows
* Closing a window will no longer recursively terminate contained multiplexer client panes; the window will instead be restored when you next connect to that multiplexer server. Killing/closing individual tabs/panes *will* kill the panes; this change only affects closing the window. [#848](https://github.com/wez/wezterm/issues/848) [#917](https://github.com/wez/wezterm/issues/917) [#1224](https://github.com/wez/wezterm/issues/1224)
### 20211205-192649-672c1cc1 ### 20211205-192649-672c1cc1

View File

@ -79,6 +79,11 @@ pub trait Domain: Downcast {
/// Indicates the state of the domain /// Indicates the state of the domain
fn state(&self) -> DomainState; fn state(&self) -> DomainState;
/// Called to advise the domain that a local window is closing.
/// This allows the domain the opportunity to eg: detach/hide
/// its tabs/panes rather than actually killing them off
fn local_window_is_closing(&self, _window_id: WindowId) {}
} }
impl_downcast!(Domain); impl_downcast!(Domain);

View File

@ -453,6 +453,10 @@ impl Mux {
fn remove_window_internal(&self, window_id: WindowId) { fn remove_window_internal(&self, window_id: WindowId) {
log::debug!("remove_window_internal {}", window_id); log::debug!("remove_window_internal {}", window_id);
let domains: Vec<Arc<dyn Domain>> = self.domains.borrow().values().cloned().collect();
for dom in domains {
dom.local_window_is_closing(window_id);
}
let window = self.windows.borrow_mut().remove(&window_id); let window = self.windows.borrow_mut().remove(&window_id);
if let Some(window) = window { if let Some(window) = window {
for tab in window.iter() { for tab in window.iter() {
@ -475,6 +479,7 @@ impl Mux {
pub fn prune_dead_windows(&self) { pub fn prune_dead_windows(&self) {
if Activity::count() > 0 { if Activity::count() > 0 {
log::trace!("prune_dead_windows: Activity::count={}", Activity::count());
return; return;
} }
let live_tab_ids: Vec<TabId> = self.tabs.borrow().keys().cloned().collect(); let live_tab_ids: Vec<TabId> = self.tabs.borrow().keys().cloned().collect();
@ -486,13 +491,14 @@ impl Mux {
Ok(w) => w, Ok(w) => w,
Err(_) => { Err(_) => {
// It's ok if our caller already locked it; we can prune later. // It's ok if our caller already locked it; we can prune later.
log::trace!("prune_dead_windows: self.windows already borrowed");
return; return;
} }
}; };
for (window_id, win) in windows.iter_mut() { for (window_id, win) in windows.iter_mut() {
win.prune_dead_tabs(&live_tab_ids); win.prune_dead_tabs(&live_tab_ids);
if win.is_empty() { if win.is_empty() {
log::debug!("prune_dead_windows: window is now empty"); log::trace!("prune_dead_windows: window is now empty");
dead_windows.push(*window_id); dead_windows.push(*window_id);
} }
} }
@ -516,12 +522,16 @@ impl Mux {
} }
if self.is_empty() { if self.is_empty() {
log::trace!("prune_dead_windows: is_empty, send MuxNotification::Empty");
self.notify(MuxNotification::Empty); self.notify(MuxNotification::Empty);
} else {
log::trace!("prune_dead_windows: not empty");
} }
} }
pub fn kill_window(&self, window_id: WindowId) { pub fn kill_window(&self, window_id: WindowId) {
self.remove_window_internal(window_id); self.remove_window_internal(window_id);
self.prune_dead_windows();
} }
pub fn get_window(&self, window_id: WindowId) -> Option<Ref<Window>> { pub fn get_window(&self, window_id: WindowId) -> Option<Ref<Window>> {

View File

@ -1,5 +1,5 @@
use crate::domain::DomainId; use crate::domain::DomainId;
use crate::pane::{Pane, PaneId, Pattern, SearchResult}; use crate::pane::{CloseReason, Pane, PaneId, Pattern, SearchResult};
use crate::renderable::*; use crate::renderable::*;
use crate::tmux::{TmuxDomain, TmuxDomainState}; use crate::tmux::{TmuxDomain, TmuxDomainState};
use crate::{Domain, Mux, MuxNotification}; use crate::{Domain, Mux, MuxNotification};
@ -316,7 +316,7 @@ impl Pane for LocalPane {
.or_else(|| self.divine_current_working_dir()) .or_else(|| self.divine_current_working_dir())
} }
fn can_close_without_prompting(&self) -> bool { fn can_close_without_prompting(&self, _reason: CloseReason) -> bool {
if let Some(proc_list) = self.divine_process_list() { if let Some(proc_list) = self.divine_process_list() {
log::trace!( log::trace!(
"can_close_without_prompting? procs in pane {:#?}", "can_close_without_prompting? procs in pane {:#?}",

View File

@ -42,6 +42,17 @@ pub struct SearchResult {
pub use config::keyassignment::Pattern; pub use config::keyassignment::Pattern;
/// Why a close request is being made
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum CloseReason {
/// The containing window is being closed
Window,
/// The containing tab is being close
Tab,
/// Just this tab is being closed
Pane,
}
const PASTE_CHUNK_SIZE: usize = 1024; const PASTE_CHUNK_SIZE: usize = 1024;
struct Paste { struct Paste {
@ -328,7 +339,7 @@ pub trait Pane: Downcast {
fn focus_changed(&self, _focused: bool) {} fn focus_changed(&self, _focused: bool) {}
/// Certain panes are OK to be closed with impunity (no prompts) /// Certain panes are OK to be closed with impunity (no prompts)
fn can_close_without_prompting(&self) -> bool { fn can_close_without_prompting(&self, _reason: CloseReason) -> bool {
false false
} }

View File

@ -1235,10 +1235,10 @@ impl Tab {
} }
} }
pub fn can_close_without_prompting(&self) -> bool { pub fn can_close_without_prompting(&self, reason: CloseReason) -> bool {
let panes = self.iter_panes(); let panes = self.iter_panes();
for pos in &panes { for pos in &panes {
if !pos.pane.can_close_without_prompting() { if !pos.pane.can_close_without_prompting(reason) {
return false; return false;
} }
} }

View File

@ -4,7 +4,7 @@
//! session. //! session.
use crate::domain::{alloc_domain_id, Domain, DomainId, DomainState}; use crate::domain::{alloc_domain_id, Domain, DomainId, DomainState};
use crate::pane::{alloc_pane_id, Pane, PaneId}; use crate::pane::{alloc_pane_id, CloseReason, Pane, PaneId};
use crate::renderable::*; use crate::renderable::*;
use crate::tab::{SplitDirection, Tab, TabId}; use crate::tab::{SplitDirection, Tab, TabId};
use crate::window::WindowId; use crate::window::WindowId;
@ -162,7 +162,7 @@ impl Pane for TermWizTerminalPane {
self.terminal.borrow_mut().get_title().to_string() self.terminal.borrow_mut().get_title().to_string()
} }
fn can_close_without_prompting(&self) -> bool { fn can_close_without_prompting(&self, _reason: CloseReason) -> bool {
true true
} }

View File

@ -1,3 +1,4 @@
use crate::pane::CloseReason;
use crate::{Mux, MuxNotification, Tab, TabId}; use crate::{Mux, MuxNotification, Tab, TabId};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@ -80,7 +81,7 @@ impl Window {
pub fn can_close_without_prompting(&self) -> bool { pub fn can_close_without_prompting(&self) -> bool {
for tab in &self.tabs { for tab in &self.tabs {
if !tab.can_close_without_prompting() { if !tab.can_close_without_prompting(CloseReason::Window) {
return false; return false;
} }
} }

View File

@ -522,6 +522,24 @@ impl Domain for ClientDomain {
Ok(()) Ok(())
} }
fn local_window_is_closing(&self, window_id: WindowId) {
let mux = Mux::get().expect("to be called by mux on mux thread");
let window = match mux.get_window(window_id) {
Some(w) => w,
None => return,
};
for tab in window.iter() {
for pos in tab.iter_panes_ignoring_zoom() {
if pos.pane.domain_id() == self.local_domain_id {
if let Some(client_pane) = pos.pane.downcast_ref::<ClientPane>() {
client_pane.ignore_next_kill();
}
}
}
}
}
fn detach(&self) -> anyhow::Result<()> { fn detach(&self) -> anyhow::Result<()> {
bail!("detach not implemented"); bail!("detach not implemented");
} }

View File

@ -6,7 +6,7 @@ use async_trait::async_trait;
use codec::*; use codec::*;
use config::configuration; use config::configuration;
use mux::domain::DomainId; use mux::domain::DomainId;
use mux::pane::{alloc_pane_id, Pane, PaneId, Pattern, SearchResult}; use mux::pane::{alloc_pane_id, CloseReason, Pane, PaneId, Pattern, SearchResult};
use mux::renderable::{RenderableDimensions, StableCursorPosition}; use mux::renderable::{RenderableDimensions, StableCursorPosition};
use mux::tab::TabId; use mux::tab::TabId;
use mux::{Mux, MuxNotification}; use mux::{Mux, MuxNotification};
@ -35,6 +35,7 @@ pub struct ClientPane {
mouse: Rc<RefCell<MouseState>>, mouse: Rc<RefCell<MouseState>>,
clipboard: RefCell<Option<Arc<dyn Clipboard>>>, clipboard: RefCell<Option<Arc<dyn Clipboard>>>,
mouse_grabbed: RefCell<bool>, mouse_grabbed: RefCell<bool>,
ignore_next_kill: RefCell<bool>,
} }
impl ClientPane { impl ClientPane {
@ -90,6 +91,7 @@ impl ClientPane {
palette: RefCell::new(palette), palette: RefCell::new(palette),
clipboard: RefCell::new(None), clipboard: RefCell::new(None),
mouse_grabbed: RefCell::new(false), mouse_grabbed: RefCell::new(false),
ignore_next_kill: RefCell::new(false),
} }
} }
@ -151,6 +153,17 @@ impl ClientPane {
pub fn remote_pane_id(&self) -> TabId { pub fn remote_pane_id(&self) -> TabId {
self.remote_pane_id self.remote_pane_id
} }
/// Arrange to suppress the next Pane::kill call.
/// This is a bit of a hack that we use when closing a window;
/// our Domain::local_window_is_closing impl calls this for each
/// ClientPane in the window so that closing a window effectively
/// "detaches" the window so that reconnecting later will resume
/// from where they left off.
/// It isn't perfect.
pub fn ignore_next_kill(&self) {
*self.ignore_next_kill.borrow_mut() = true;
}
} }
#[async_trait(?Send)] #[async_trait(?Send)]
@ -332,6 +345,11 @@ impl Pane for ClientPane {
} }
fn kill(&self) { fn kill(&self) {
let mut ignore = self.ignore_next_kill.borrow_mut();
if *ignore {
*ignore = false;
return;
}
let client = Arc::clone(&self.client); let client = Arc::clone(&self.client);
let remote_pane_id = self.remote_pane_id; let remote_pane_id = self.remote_pane_id;
promise::spawn::spawn(async move { promise::spawn::spawn(async move {
@ -387,6 +405,14 @@ impl Pane for ClientPane {
fn get_current_working_dir(&self) -> Option<Url> { fn get_current_working_dir(&self) -> Option<Url> {
self.renderable.borrow().inner.borrow().working_dir.clone() self.renderable.borrow().inner.borrow().working_dir.clone()
} }
fn can_close_without_prompting(&self, reason: CloseReason) -> bool {
match reason {
CloseReason::Window => true,
CloseReason::Tab => false,
CloseReason::Pane => false,
}
}
} }
struct PaneWriter { struct PaneWriter {

View File

@ -28,7 +28,7 @@ use config::{
use luahelper::impl_lua_conversion; use luahelper::impl_lua_conversion;
use mlua::FromLua; use mlua::FromLua;
use mux::domain::{DomainId, DomainState}; use mux::domain::{DomainId, DomainState};
use mux::pane::{Pane, PaneId}; use mux::pane::{CloseReason, Pane, PaneId};
use mux::renderable::RenderableDimensions; use mux::renderable::RenderableDimensions;
use mux::tab::{PositionedPane, PositionedSplit, SplitDirection, Tab, TabId}; use mux::tab::{PositionedPane, PositionedSplit, SplitDirection, Tab, TabId};
use mux::window::WindowId as MuxWindowId; use mux::window::WindowId as MuxWindowId;
@ -2064,7 +2064,7 @@ impl TermWindow {
}; };
let pane_id = pane.pane_id(); let pane_id = pane.pane_id();
if confirm && !pane.can_close_without_prompting() { if confirm && !pane.can_close_without_prompting(CloseReason::Pane) {
let window = self.window.clone().unwrap(); let window = self.window.clone().unwrap();
let (overlay, future) = start_overlay_pane(self, &pane, move |pane_id, term| { let (overlay, future) = start_overlay_pane(self, &pane, move |pane_id, term| {
confirm_close_pane(pane_id, term, mux_window_id, window) confirm_close_pane(pane_id, term, mux_window_id, window)
@ -2084,7 +2084,7 @@ impl TermWindow {
}; };
let tab_id = tab.tab_id(); let tab_id = tab.tab_id();
let mux_window_id = self.mux_window_id; let mux_window_id = self.mux_window_id;
if confirm && !tab.can_close_without_prompting() { if confirm && !tab.can_close_without_prompting(CloseReason::Tab) {
let window = self.window.clone().unwrap(); let window = self.window.clone().unwrap();
let (overlay, future) = start_overlay(self, &tab, move |tab_id, term| { let (overlay, future) = start_overlay(self, &tab, move |tab_id, term| {
confirm_close_tab(tab_id, term, mux_window_id, window) confirm_close_tab(tab_id, term, mux_window_id, window)