1
1
mirror of https://github.com/wez/wezterm.git synced 2025-01-08 23:17:36 +03:00

Windows can be labelled with a workspace name

This is not exposed through any UX; the mux api allows setting
the workspace and propagating information about windows whose
workspace has changed.

Windows start with a blank workspace name.

This is just plumbing; nothing uses it yet.

refs: #1531
This commit is contained in:
Wez Furlong 2022-01-12 16:28:32 -07:00
parent f6e56aab10
commit cefe45d206
10 changed files with 209 additions and 5 deletions

View File

@ -406,7 +406,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 = 14;
pub const CODEC_VERSION: usize = 15;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
@ -448,6 +448,8 @@ pdu! {
SetClientId: 40,
GetClientList: 41,
GetClientListResponse: 42,
SetWindowWorkspace: 43,
WindowWorkspaceChanged: 44,
}
impl Pdu {
@ -688,6 +690,12 @@ pub struct SetClipboard {
pub selection: ClipboardSelection,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetWindowWorkspace {
pub window_id: WindowId,
pub workspace: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetPalette {
pub pane_id: PaneId,
@ -700,6 +708,12 @@ pub struct NotifyAlert {
pub alert: Alert,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct WindowWorkspaceChanged {
pub window_id: WindowId,
pub workspace: String,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct SetClientId {
pub client_id: ClientId,

View File

@ -48,6 +48,7 @@ pub enum MuxNotification {
WindowCreated(WindowId),
WindowRemoved(WindowId),
WindowInvalidated(WindowId),
WindowWorkspaceChanged(WindowId),
Alert {
pane_id: PaneId,
alert: wezterm_term::Alert,
@ -627,6 +628,21 @@ impl Mux {
.collect()
}
pub fn iter_windows_in_workspace(&self, workspace: &str) -> Vec<WindowId> {
self.windows
.borrow()
.iter()
.filter_map(|(k, w)| {
if w.get_workspace() == workspace {
Some(k)
} else {
None
}
})
.cloned()
.collect()
}
pub fn iter_windows(&self) -> Vec<WindowId> {
self.windows.borrow().keys().cloned().collect()
}

View File

@ -156,12 +156,17 @@ fn pane_tree(
window_id: WindowId,
active: Option<&Rc<dyn Pane>>,
zoomed: Option<&Rc<dyn Pane>>,
workspace: &str,
) -> PaneNode {
match tree {
Tree::Empty => PaneNode::Empty,
Tree::Node { left, right, data } => PaneNode::Split {
left: Box::new(pane_tree(&*left, tab_id, window_id, active, zoomed)),
right: Box::new(pane_tree(&*right, tab_id, window_id, active, zoomed)),
left: Box::new(pane_tree(
&*left, tab_id, window_id, active, zoomed, workspace,
)),
right: Box::new(pane_tree(
&*right, tab_id, window_id, active, zoomed, workspace,
)),
node: data.unwrap(),
},
Tree::Leaf(pane) => {
@ -182,6 +187,7 @@ fn pane_tree(
pixel_width: 0,
},
working_dir: working_dir.map(Into::into),
workspace: workspace.to_string(),
})
}
}
@ -484,10 +490,28 @@ impl Tab {
}
};
let workspace = match mux
.get_window(window_id)
.map(|w| w.get_workspace().to_string())
{
Some(ws) => ws,
None => {
log::error!("window id {} doesn't have a window!?", window_id);
return PaneNode::Empty;
}
};
let zoomed = self.zoomed.borrow();
let active = self.get_active_pane();
if let Some(root) = self.pane.borrow().as_ref() {
pane_tree(root, tab_id, window_id, active.as_ref(), zoomed.as_ref())
pane_tree(
root,
tab_id,
window_id,
active.as_ref(),
zoomed.as_ref(),
&workspace,
)
} else {
PaneNode::Empty
}
@ -1539,6 +1563,7 @@ pub struct PaneEntry {
pub working_dir: Option<SerdeUrl>,
pub is_active_pane: bool,
pub is_zoomed_pane: bool,
pub workspace: String,
}
#[derive(Deserialize, Clone, Serialize, PartialEq, Debug)]

View File

@ -13,6 +13,7 @@ pub struct Window {
active: usize,
last_active: Option<TabId>,
clipboard: Option<Arc<dyn Clipboard>>,
workspace: String,
}
impl Window {
@ -23,6 +24,21 @@ impl Window {
active: 0,
last_active: None,
clipboard: None,
workspace: "".to_string(),
}
}
pub fn get_workspace(&self) -> &str {
&self.workspace
}
pub fn set_workspace(&mut self, workspace: &str) {
if workspace == self.workspace {
return;
}
self.workspace = workspace.to_string();
if let Some(mux) = Mux::get() {
mux.notify(MuxNotification::WindowWorkspaceChanged(self.id));
}
}

View File

@ -179,6 +179,41 @@ fn process_unilateral(
return Ok(());
}
};
match &decoded.pdu {
Pdu::WindowWorkspaceChanged(WindowWorkspaceChanged {
window_id,
workspace,
}) => {
let window_id = *window_id;
let workspace = workspace.to_string();
promise::spawn::spawn_into_main_thread(async move {
let mux = Mux::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)
})?;
let local_window_id = client_domain
.remote_to_local_window_id(window_id)
.ok_or_else(|| anyhow!("no local window for remote window id {}", window_id))?;
if let Some(mut window) = mux.get_window_mut(local_window_id) {
window.set_workspace(&workspace);
}
anyhow::Result::<()>::Ok(())
})
.detach();
return Ok(());
}
_ => {}
}
if let Some(pane_id) = decoded.pdu.pane_id() {
promise::spawn::spawn_into_main_thread(async move {
process_unilateral_inner(pane_id, local_domain_id, decoded)
@ -1127,4 +1162,5 @@ impl Client {
rpc!(kill_pane, KillPane, UnitResponse);
rpc!(set_client_id, SetClientId, UnitResponse);
rpc!(list_clients, GetClientList, GetClientListResponse);
rpc!(set_window_workspace, SetWindowWorkspace, UnitResponse);
}

View File

@ -10,7 +10,7 @@ use mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
use mux::pane::{Pane, PaneId};
use mux::tab::{SplitDirection, Tab, TabId};
use mux::window::WindowId;
use mux::Mux;
use mux::{Mux, MuxNotification};
use portable_pty::{CommandBuilder, PtySize};
use promise::spawn::spawn_into_new_thread;
use std::cell::RefCell;
@ -176,10 +176,55 @@ pub struct ClientDomain {
local_domain_id: DomainId,
}
async fn update_remote_workspace(
local_domain_id: DomainId,
pdu: codec::SetWindowWorkspace,
) -> anyhow::Result<()> {
let inner = ClientDomain::get_client_inner_for_domain(local_domain_id)?;
inner.client.set_window_workspace(pdu).await?;
Ok(())
}
fn mux_notify_client_domain(local_domain_id: DomainId, notif: MuxNotification) -> bool {
let mux = Mux::get().expect("called by mux");
let domain = match mux.get_domain(local_domain_id) {
Some(domain) => domain,
None => return false,
};
let domain = match domain.downcast_ref::<ClientDomain>() {
Some(domain) => domain,
None => return false,
};
match notif {
MuxNotification::WindowWorkspaceChanged(window_id) => {
if let Some(remote_window_id) = domain.local_to_remote_window_id(window_id) {
if let Some(workspace) = mux
.get_window(window_id)
.map(|w| w.get_workspace().to_string())
{
let request = codec::SetWindowWorkspace {
window_id: remote_window_id,
workspace,
};
promise::spawn::spawn_into_main_thread(async move {
let _ = update_remote_workspace(local_domain_id, request).await;
})
.detach();
}
}
}
_ => {}
}
true
}
impl ClientDomain {
pub fn new(config: ClientDomainConfig) -> Self {
let local_domain_id = alloc_domain_id();
let label = config.label();
Mux::get()
.expect("created on main thread")
.subscribe(move |notif| mux_notify_client_domain(local_domain_id, notif));
Self {
config,
label,
@ -208,6 +253,16 @@ impl ClientDomain {
inner.remote_to_local_pane_id(remote_pane_id)
}
pub fn remote_to_local_window_id(&self, remote_window_id: WindowId) -> Option<WindowId> {
let inner = self.inner()?;
inner.remote_to_local_window(remote_window_id)
}
pub fn local_to_remote_window_id(&self, local_window_id: WindowId) -> Option<WindowId> {
let inner = self.inner()?;
inner.local_to_remote_window(local_window_id)
}
pub fn get_client_inner_for_domain(domain_id: DomainId) -> anyhow::Result<Arc<ClientInner>> {
let mux = Mux::get().unwrap();
let domain = mux

View File

@ -38,6 +38,7 @@ impl GuiFrontEnd {
})
.detach();
}
MuxNotification::WindowWorkspaceChanged(_) => {}
MuxNotification::WindowRemoved(_) => {}
MuxNotification::PaneRemoved(_) => {}
MuxNotification::WindowInvalidated(_) => {}

View File

@ -99,6 +99,22 @@ where
Ok(Item::Notif(MuxNotification::WindowRemoved(_window_id))) => {}
Ok(Item::Notif(MuxNotification::WindowCreated(_window_id))) => {}
Ok(Item::Notif(MuxNotification::WindowInvalidated(_window_id))) => {}
Ok(Item::Notif(MuxNotification::WindowWorkspaceChanged(window_id))) => {
let workspace = {
let mux = Mux::get().expect("to be running on gui thread");
mux.get_window(window_id)
.map(|w| w.get_workspace().to_string())
};
if let Some(workspace) = workspace {
Pdu::WindowWorkspaceChanged(codec::WindowWorkspaceChanged {
window_id,
workspace,
})
.encode_async(&mut stream, 0)
.await?;
stream.flush().await.context("flushing PDU to client")?;
}
}
Ok(Item::Notif(MuxNotification::Empty)) => {}
Err(err) => {
log::error!("process_async Err {}", err);

View File

@ -281,6 +281,25 @@ impl SessionHandler {
match decoded.pdu {
Pdu::Ping(Ping {}) => send_response(Ok(Pdu::Pong(Pong {}))),
Pdu::SetWindowWorkspace(SetWindowWorkspace {
window_id,
workspace,
}) => {
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get().unwrap();
let mut window = mux
.get_window_mut(window_id)
.ok_or_else(|| anyhow!("window {} is invalid", window_id))?;
window.set_workspace(&workspace);
Ok(Pdu::UnitResponse(UnitResponse {}))
},
send_response,
)
})
.detach();
}
Pdu::SetClientId(SetClientId { client_id }) => {
self.client_id.replace(client_id.clone());
spawn_into_main_thread(async move {
@ -632,6 +651,7 @@ impl SessionHandler {
| Pdu::SearchScrollbackResponse { .. }
| Pdu::GetLinesResponse { .. }
| Pdu::GetCodecVersionResponse { .. }
| Pdu::WindowWorkspaceChanged { .. }
| Pdu::GetTlsCredsResponse { .. }
| Pdu::GetClientListResponse { .. }
| Pdu::PaneRemoved { .. }

View File

@ -472,6 +472,10 @@ async fn run_cli_async(config: config::ConfigHandle, cli: CliCommand) -> anyhow:
name: "PANEID".to_string(),
alignment: Alignment::Right,
},
Column {
name: "WORKSPACE".to_string(),
alignment: Alignment::Left,
},
Column {
name: "SIZE".to_string(),
alignment: Alignment::Left,
@ -497,6 +501,7 @@ async fn run_cli_async(config: config::ConfigHandle, cli: CliCommand) -> anyhow:
entry.window_id.to_string(),
entry.tab_id.to_string(),
entry.pane_id.to_string(),
entry.workspace.to_string(),
format!("{}x{}", entry.size.cols, entry.size.rows),
entry.title.clone(),
entry