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

mux: propagate tab and window title when it changes

refs: #1598
This commit is contained in:
Wez Furlong 2023-04-01 21:02:58 -07:00
parent ac3e2307d2
commit dd7d22ed6b
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
12 changed files with 258 additions and 14 deletions

View File

@ -23,6 +23,7 @@ use rangeset::*;
use serde::{Deserialize, Serialize};
use smol::io::AsyncWriteExt;
use smol::prelude::*;
use std::collections::HashMap;
use std::convert::TryInto;
use std::io::Cursor;
use std::ops::Range;
@ -417,7 +418,7 @@ macro_rules! pdu {
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 36;
pub const CODEC_VERSION: usize = 37;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
@ -471,6 +472,8 @@ pdu! {
PaneFocused: 53,
TabResized: 54,
TabAddedToWindow: 55,
TabTitleChanged: 56,
WindowTitleChanged: 57,
}
impl Pdu {
@ -597,6 +600,8 @@ pub struct ListPanes {}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct ListPanesResponse {
pub tabs: Vec<PaneNode>,
pub tab_titles: Vec<String>,
pub window_titles: HashMap<WindowId, String>,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
@ -751,6 +756,18 @@ pub struct TabResized {
pub tab_id: TabId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct TabTitleChanged {
pub tab_id: TabId,
pub title: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct WindowTitleChanged {
pub window_id: WindowId,
pub title: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct PaneFocused {
pub pane_id: PaneId,

View File

@ -65,6 +65,8 @@ As features stabilize some brief notes about them will accumulate here.
* [window:perform_action()](config/lua/window/perform_action.md) now correctly
resolves overlay panes such as Copy Mode. #3209
* macOS: CTRL-Q had to be pressed twice to register when `use_ime=true`. #2630
* mux: [tab:set_title()](config/lua/MuxTab/set_title.md) didn't get passed to
the remote server, so any tab title changes were lost when reconnecting. #1598
### 20230326-111934-3666303c

View File

@ -79,6 +79,14 @@ pub enum MuxNotification {
},
PaneFocused(PaneId),
TabResized(TabId),
TabTitleChanged {
tab_id: TabId,
title: String,
},
WindowTitleChanged {
window_id: WindowId,
title: String,
},
}
static SUB_ID: AtomicUsize = AtomicUsize::new(0);

View File

@ -521,7 +521,15 @@ impl Tab {
pub fn set_title(&self, title: &str) {
let mut inner = self.inner.lock();
inner.title = title.to_string();
if inner.title != title {
inner.title = title.to_string();
Mux::try_get().map(|mux| {
mux.notify(MuxNotification::TabTitleChanged {
tab_id: inner.id,
title: title.to_string(),
})
});
}
}
/// Called by the multiplexer client when building a local tab to

View File

@ -38,7 +38,15 @@ impl Window {
}
pub fn set_title(&mut self, title: &str) {
self.title = title.to_string();
if self.title != title {
self.title = title.to_string();
Mux::try_get().map(|mux| {
mux.notify(MuxNotification::WindowTitleChanged {
window_id: self.id,
title: title.to_string(),
})
});
}
}
pub fn get_title(&self) -> &str {

View File

@ -235,6 +235,48 @@ fn process_unilateral(
return Ok(());
}
Pdu::WindowTitleChanged(WindowTitleChanged { window_id, title }) => {
let title = title.to_string();
let window_id = *window_id;
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::<ClientDomain>()
.ok_or_else(|| {
anyhow!("domain {} is not a ClientDomain instance", local_domain_id)
})?;
client_domain.process_remote_window_title_change(window_id, title);
anyhow::Result::<()>::Ok(())
})
.detach();
return Ok(());
}
Pdu::TabTitleChanged(TabTitleChanged { tab_id, title }) => {
let title = title.to_string();
let tab_id = *tab_id;
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::<ClientDomain>()
.ok_or_else(|| {
anyhow!("domain {} is not a ClientDomain instance", local_domain_id)
})?;
client_domain.process_remote_tab_title_change(tab_id, title);
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 {
@ -1257,4 +1299,6 @@ impl Client {
rpc!(set_focused_pane_id, SetFocusedPane, UnitResponse);
rpc!(get_image_cell, GetImageCell, GetImageCellResponse);
rpc!(set_configured_palette_for_pane, SetPalette, UnitResponse);
rpc!(set_tab_title, TabTitleChanged, UnitResponse);
rpc!(set_window_title, WindowTitleChanged, UnitResponse);
}

View File

@ -100,6 +100,16 @@ impl ClientInner {
);
}
fn local_to_remote_tab(&self, local_tab_id: TabId) -> Option<TabId> {
let map = self.remote_to_local_tab.lock().unwrap();
for (remote, local) in map.iter() {
if *local == local_tab_id {
return Some(*remote);
}
}
None
}
fn local_to_remote_window(&self, local_window_id: WindowId) -> Option<WindowId> {
let map = self.remote_to_local_window.lock().unwrap();
for (remote, local) in map.iter() {
@ -269,9 +279,11 @@ fn mux_notify_client_domain(local_domain_id: DomainId, notif: MuxNotification) -
Some(domain) => domain,
None => return false,
};
if domain.downcast_ref::<ClientDomain>().is_none() {
return false;
}
let client_domain = match domain.downcast_ref::<ClientDomain>() {
Some(c) => c,
None => return false,
};
match notif {
MuxNotification::ActiveWorkspaceChanged(_client_id) => {
// TODO: advice remote host of interesting workspaces
@ -313,6 +325,38 @@ fn mux_notify_client_domain(local_domain_id: DomainId, notif: MuxNotification) -
})
.detach();
}
MuxNotification::TabTitleChanged { tab_id, title } => {
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_tab_title(codec::TabTitleChanged {
tab_id: remote_tab_id,
title,
})
.await
})
.detach();
}
}
}
MuxNotification::WindowTitleChanged { window_id, title } => {
if let Some(remote_window_id) = client_domain.local_to_remote_window_id(window_id) {
if let Some(inner) = client_domain.inner() {
promise::spawn::spawn(async move {
inner
.client
.set_window_title(codec::WindowTitleChanged {
window_id: remote_window_id,
title,
})
.await
})
.detach();
}
}
}
_ => {}
}
true
@ -361,6 +405,11 @@ impl ClientDomain {
inner.local_to_remote_window(local_window_id)
}
pub fn local_to_remote_tab_id(&self, local_tab_id: TabId) -> Option<TabId> {
let inner = self.inner()?;
inner.local_to_remote_tab(local_tab_id)
}
pub fn get_client_inner_for_domain(domain_id: DomainId) -> anyhow::Result<Arc<ClientInner>> {
let mux = Mux::get();
let domain = mux
@ -399,6 +448,26 @@ impl ClientDomain {
Ok(())
}
pub fn process_remote_window_title_change(&self, remote_window_id: WindowId, title: String) {
if let Some(inner) = self.inner() {
if let Some(local_window_id) = inner.remote_to_local_window(remote_window_id) {
if let Some(mut window) = Mux::get().get_window_mut(local_window_id) {
window.set_title(&title);
}
}
}
}
pub fn process_remote_tab_title_change(&self, remote_tab_id: TabId, title: String) {
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_title(&title);
}
}
}
}
fn process_pane_list(
inner: Arc<ClientInner>,
panes: ListPanesResponse,
@ -435,7 +504,7 @@ impl ClientDomain {
.copied()
.collect();
for tabroot in panes.tabs {
for (tabroot, tab_title) in panes.tabs.into_iter().zip(panes.tab_titles.iter()) {
let root_size = match tabroot.root_size() {
Some(size) => size,
None => continue,
@ -471,6 +540,8 @@ impl ClientDomain {
inner.record_remote_to_local_tab_mapping(remote_tab_id, tab.tab_id());
}
tab.set_title(tab_title);
log::debug!("domain: {} tree: {:#?}", inner.local_domain_id, tabroot);
let mut workspace = None;
tab.sync_with_pane_tree(root_size, tabroot, |entry| {
@ -565,6 +636,15 @@ impl ClientDomain {
}
}
for (remote_window_id, window_title) in panes.window_titles {
if let Some(local_window_id) = inner.remote_to_local_window(remote_window_id) {
let mut window = mux
.get_window_mut(local_window_id)
.expect("no such window!?");
window.set_title(&window_title);
}
}
// "Sweep" away our mapping for ids that are no longer present in the
// latest sync
log::debug!(

View File

@ -73,6 +73,8 @@ impl GuiFrontEnd {
})
.detach();
}
MuxNotification::TabTitleChanged { .. } => {}
MuxNotification::WindowTitleChanged { .. } => {}
MuxNotification::TabResized(_) => {}
MuxNotification::TabAddedToWindow { .. } => {}
MuxNotification::PaneRemoved(_) => {}

View File

@ -1097,7 +1097,8 @@ impl TermWindow {
} => {
self.emit_user_var_event(pane_id, name, value);
}
MuxNotification::Alert {
MuxNotification::WindowTitleChanged { .. }
| MuxNotification::Alert {
alert:
Alert::OutputSinceFocusLost
| Alert::CurrentWorkingDirectoryChanged
@ -1188,6 +1189,9 @@ impl TermWindow {
MuxNotification::TabResized(_) => {
// Handled by wezterm-client
}
MuxNotification::TabTitleChanged { .. } => {
self.update_title_post_status();
}
MuxNotification::PaneAdded(_)
| MuxNotification::PaneRemoved(_)
| MuxNotification::WindowWorkspaceChanged(_)
@ -1374,6 +1378,8 @@ impl TermWindow {
| MuxNotification::SaveToDownloads { .. }
| MuxNotification::PaneFocused(_)
| MuxNotification::TabResized(_)
| MuxNotification::TabTitleChanged { .. }
| MuxNotification::WindowTitleChanged { .. }
| MuxNotification::PaneRemoved(_)
| MuxNotification::WindowCreated(_)
| MuxNotification::ActiveWorkspaceChanged(_)

View File

@ -176,6 +176,18 @@ where
.await?;
stream.flush().await.context("flushing PDU to client")?;
}
Ok(Item::Notif(MuxNotification::TabTitleChanged { tab_id, title })) => {
Pdu::TabTitleChanged(codec::TabTitleChanged { tab_id, title })
.encode_async(&mut stream, 0)
.await?;
stream.flush().await.context("flushing PDU to client")?;
}
Ok(Item::Notif(MuxNotification::WindowTitleChanged { window_id, title })) => {
Pdu::WindowTitleChanged(codec::WindowTitleChanged { window_id, title })
.encode_async(&mut stream, 0)
.await?;
stream.flush().await.context("flushing PDU to client")?;
}
Ok(Item::Notif(MuxNotification::ActiveWorkspaceChanged(_))) => {}
Ok(Item::Notif(MuxNotification::Empty)) => {}
Err(err) => {

View File

@ -328,14 +328,22 @@ impl SessionHandler {
move || {
let mux = Mux::get();
let mut tabs = vec![];
let mut tab_titles = vec![];
let mut window_titles = HashMap::new();
for window_id in mux.iter_windows().into_iter() {
let window = mux.get_window(window_id).unwrap();
window_titles.insert(window_id, window.get_title().to_string());
for tab in window.iter() {
tabs.push(tab.codec_pane_tree());
tab_titles.push(tab.get_title());
}
}
log::trace!("ListPanes {:#?}", tabs);
Ok(Pdu::ListPanesResponse(ListPanesResponse { tabs }))
log::trace!("ListPanes {tabs:#?} {tab_titles:?}");
Ok(Pdu::ListPanesResponse(ListPanesResponse {
tabs,
tab_titles,
window_titles,
}))
},
send_response,
)
@ -730,6 +738,42 @@ impl SessionHandler {
send_response,
);
}
Pdu::WindowTitleChanged(WindowTitleChanged { window_id, title }) => {
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get();
let mut window = mux
.get_window_mut(window_id)
.ok_or_else(|| anyhow!("no such window {window_id}"))?;
window.set_title(&title);
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
)
})
.detach();
}
Pdu::TabTitleChanged(TabTitleChanged { tab_id, title }) => {
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get();
let tab = mux
.get_tab(tab_id)
.ok_or_else(|| anyhow!("no such tab {tab_id}"))?;
tab.set_title(&title);
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
)
})
.detach();
}
Pdu::SetPalette(SetPalette { pane_id, palette }) => {
spawn_into_main_thread(async move {
catch(

View File

@ -774,10 +774,12 @@ struct CliListResultItem {
left_col: usize,
/// Number of rows from the top of the tab area to the top of this pane
top_row: usize,
tab_title: String,
window_title: String,
}
impl From<mux::tab::PaneEntry> for CliListResultItem {
fn from(pane: mux::tab::PaneEntry) -> CliListResultItem {
impl CliListResultItem {
fn from(pane: mux::tab::PaneEntry, tab_title: &str, window_title: &str) -> CliListResultItem {
let mux::tab::PaneEntry {
window_id,
tab_id,
@ -824,6 +826,8 @@ impl From<mux::tab::PaneEntry> for CliListResultItem {
cursor_visibility: cursor_pos.visibility,
left_col,
top_row,
tab_title: tab_title.to_string(),
window_title: window_title.to_string(),
}
}
}
@ -976,12 +980,21 @@ async fn run_cli_async(config: config::ConfigHandle, cli: CliCommand) -> anyhow:
let mut output_items = vec![];
let panes = client.list_panes().await?;
for tabroot in panes.tabs {
for (tabroot, tab_title) in panes.tabs.into_iter().zip(panes.tab_titles.iter()) {
let mut cursor = tabroot.into_tree().cursor();
loop {
if let Some(entry) = cursor.leaf_mut() {
output_items.push(CliListResultItem::from(entry.clone()));
let window_title = panes
.window_titles
.get(&entry.window_id)
.map(|s| s.as_str())
.unwrap_or("");
output_items.push(CliListResultItem::from(
entry.clone(),
tab_title,
window_title,
));
}
match cursor.preorder_next() {
Ok(c) => cursor = c,