mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
parent
ac3e2307d2
commit
dd7d22ed6b
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -73,6 +73,8 @@ impl GuiFrontEnd {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
MuxNotification::TabTitleChanged { .. } => {}
|
||||
MuxNotification::WindowTitleChanged { .. } => {}
|
||||
MuxNotification::TabResized(_) => {}
|
||||
MuxNotification::TabAddedToWindow { .. } => {}
|
||||
MuxNotification::PaneRemoved(_) => {}
|
||||
|
@ -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(_)
|
||||
|
@ -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) => {
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user