diff --git a/.gitignore b/.gitignore index 59509c753..ebb8912ce 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ dhat-heap.json /docs/config/lua/keyassignment/CopyMode/index.md /docs/config/lua/mux-events/index.md /docs/config/lua/mux-window/index.md +/docs/config/lua/MuxDomain/index.md /docs/config/lua/MuxTab/index.md /docs/config/lua/MuxPane/index.md /docs/config/lua/pane/index.md diff --git a/ci/generate-docs.py b/ci/generate-docs.py index 742e708e6..db47ec1d1 100644 --- a/ci/generate-docs.py +++ b/ci/generate-docs.py @@ -391,6 +391,7 @@ TOC = [ Gen("object: Color", "config/lua/color"), Page("object: ExecDomain", "config/lua/ExecDomain.md"), Page("object: LocalProcessInfo", "config/lua/LocalProcessInfo.md"), + Gen("object: MuxDomain", "config/lua/MuxDomain"), Gen("object: MuxWindow", "config/lua/mux-window"), Gen("object: MuxTab", "config/lua/MuxTab"), Page("object: PaneInformation", "config/lua/PaneInformation.md"), diff --git a/docs/changelog.md b/docs/changelog.md index 0fe5b4445..a4c05be1b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -21,6 +21,10 @@ As features stabilize some brief notes about them will accumulate here. [#2741](https://github.com/wez/wezterm/issues/2741) * macOS: initial cut at macOS native menu bar [#1485](https://github.com/wez/wezterm/issues/1485) +* mux: exposed [MuxDomain](config/lua/MuxDomain/index.md) to lua, along with + [wezterm.mux.get_domain()](config/lua/wezterm.mux/get_domain.md), + [wezterm.mux.all_domains()](config/lua/wezterm.mux/all_domains.md) and + [wezterm.mux.set_default_domain()](config/lua/wezterm.mux/set_default_domain.md). #### Fixed * X11: hanging or killing the IME could hang wezterm diff --git a/docs/config/lua/MuxDomain/attach.md b/docs/config/lua/MuxDomain/attach.md new file mode 100644 index 000000000..7d12ac2c8 --- /dev/null +++ b/docs/config/lua/MuxDomain/attach.md @@ -0,0 +1,17 @@ +# `domain:attach()` + +*Since: nightly builds only* + +Attempts to attach the domain. + +Attaching a domain will attempt to import the windows, tabs and panes from the +remote system into those of the local GUI. + +Unlike the [AttachDomain](../keyassignment/AttachDomain.md) key assignment, +calling `domain:attach()` will *not* implicitly spawn a new pane into the +domain if the domain contains no panes. This is to provide flexibility when +used in the [gui-startup](../gui-events/gui-startup.md) event. + +If the domain is already attached, calling this method again has no effect. + +See also: [domain:detach()](detach.md) and [domain:state()](state.md). diff --git a/docs/config/lua/MuxDomain/detach.md b/docs/config/lua/MuxDomain/detach.md new file mode 100644 index 000000000..9ef0beada --- /dev/null +++ b/docs/config/lua/MuxDomain/detach.md @@ -0,0 +1,12 @@ +# `domain:detach()` + +*Since: nightly builds only* + +Attempts to detach the domain. + +Detaching a domain causes it to disconnect and remove its set of windows, tabs +and panes from the local GUI. Detaching does not cause those panes to close; if +or when you later attach to the domain, they'll still be there. + +Not every domain supports detaching, and will log an error to the error +log/debug overlay. diff --git a/docs/config/lua/MuxDomain/domain_id.md b/docs/config/lua/MuxDomain/domain_id.md new file mode 100644 index 000000000..bda60976b --- /dev/null +++ b/docs/config/lua/MuxDomain/domain_id.md @@ -0,0 +1,6 @@ +# `domain:domain_id()` + +*Since: nightly builds only* + +Returns the domain id. + diff --git a/docs/config/lua/MuxDomain/has_any_panes.md b/docs/config/lua/MuxDomain/has_any_panes.md new file mode 100644 index 000000000..7ccdc8ea3 --- /dev/null +++ b/docs/config/lua/MuxDomain/has_any_panes.md @@ -0,0 +1,9 @@ +# `domain:has_any_panes()` + +*Since: nightly builds only* + +Returns `true` if the mux has any panes that belong to this domain. + +This can be useful when deciding whether to spawn additional panes after +attaching to a domain. + diff --git a/docs/config/lua/MuxDomain/index.markdown b/docs/config/lua/MuxDomain/index.markdown new file mode 100644 index 000000000..30ac84c99 --- /dev/null +++ b/docs/config/lua/MuxDomain/index.markdown @@ -0,0 +1,8 @@ +# MuxDomain + +*Since: nightly builds only* + +`MuxDomain` represents a domain that is managed by the multiplexer. + +It has the following methods: + diff --git a/docs/config/lua/MuxDomain/is_spawnable.md b/docs/config/lua/MuxDomain/is_spawnable.md new file mode 100644 index 000000000..4051feb93 --- /dev/null +++ b/docs/config/lua/MuxDomain/is_spawnable.md @@ -0,0 +1,9 @@ +# `domain:is_spawnable()` + +*Since: nightly builds only* + +Returns `false` if this domain will never be able to spawn a new pane/tab/window, `true` otherwise. + +Serial ports are represented by a serial domain that is not spawnable. + + diff --git a/docs/config/lua/MuxDomain/label.md b/docs/config/lua/MuxDomain/label.md new file mode 100644 index 000000000..dc7b1910f --- /dev/null +++ b/docs/config/lua/MuxDomain/label.md @@ -0,0 +1,9 @@ +# `domain:label()` + +*Since: nightly builds only* + +Computes a label describing the name and state of the domain. +The label can change depending on the state of the domain. + +See also: [domain:name()](name.md). + diff --git a/docs/config/lua/MuxDomain/name.md b/docs/config/lua/MuxDomain/name.md new file mode 100644 index 000000000..ee3b8b27e --- /dev/null +++ b/docs/config/lua/MuxDomain/name.md @@ -0,0 +1,8 @@ +# `domain:name()` + +*Since: nightly builds only* + +Returns the name of the domain. Domain names are unique; no two domains can +have the same name, and the name is fixed for the lifetime of the domain. + +See also: [domain:label()](label.md). diff --git a/docs/config/lua/MuxDomain/state.md b/docs/config/lua/MuxDomain/state.md new file mode 100644 index 000000000..1e3b998ca --- /dev/null +++ b/docs/config/lua/MuxDomain/state.md @@ -0,0 +1,10 @@ +# `domain:state()` + +*Since: nightly builds only* + +Returns whether the domain is attached or not. The result is a string that is either: + +* `"Attached"` - the domain is attached +* `"Detached"` - the domain is not attached + +See also: [domain:detach()](detach.md) and [domain:detach()](detach.md). diff --git a/docs/config/lua/wezterm.mux/all_domains.md b/docs/config/lua/wezterm.mux/all_domains.md new file mode 100644 index 000000000..cbd168d9a --- /dev/null +++ b/docs/config/lua/wezterm.mux/all_domains.md @@ -0,0 +1,6 @@ +# `wezterm.mux.all_domains()` + +*Since: nightly builds only* + +Returns an array table holding all of the known +[MuxDomain](../MuxDomain/index.md) objects. diff --git a/docs/config/lua/wezterm.mux/get_domain.md b/docs/config/lua/wezterm.mux/get_domain.md new file mode 100644 index 000000000..481035a80 --- /dev/null +++ b/docs/config/lua/wezterm.mux/get_domain.md @@ -0,0 +1,16 @@ +# `wezterm.mux.get_domain(name_or_id)` + +*Since: nightly builds only* + +Resolves `name_or_id` to a domain and returns a +[MuxDomain](../MuxDomain/index.md) object representation of it. + +`name_or_id` can be: + +* A domain name string to resolve the domain by name +* A domain id to resolve the domain by id +* `nil` or omitted to return the current default domain +* other lua types will generate a lua error + +If the name or id don't map to a valid domain, this function will return `nil`. + diff --git a/docs/config/lua/wezterm.mux/set_default_domain.md b/docs/config/lua/wezterm.mux/set_default_domain.md new file mode 100644 index 000000000..46ae1353f --- /dev/null +++ b/docs/config/lua/wezterm.mux/set_default_domain.md @@ -0,0 +1,10 @@ +# `wezterm.mux.set_default_domain(MuxDomain)` + +*Since: nightly builds only* + +Assign a new default domain in the mux. + +The domain that you assign here will override any configured +[default_domain](../config/default_domain.md) or the implicit assignment of the +default domain that may have happened as a result of starting wezterm via +`wezterm connect` or `wezterm serial`. diff --git a/lua-api-crates/mux/src/domain.rs b/lua-api-crates/mux/src/domain.rs new file mode 100644 index 000000000..aa4283434 --- /dev/null +++ b/lua-api-crates/mux/src/domain.rs @@ -0,0 +1,83 @@ +use super::*; +use mux::domain::{Domain, DomainId, DomainState}; +use std::sync::Arc; + +#[derive(Clone, Copy, Debug)] +pub struct MuxDomain(pub DomainId); + +impl MuxDomain { + pub fn resolve<'a>(&self, mux: &'a Arc) -> mlua::Result> { + mux.get_domain(self.0) + .ok_or_else(|| mlua::Error::external(format!("domain id {} not found in mux", self.0))) + } +} + +impl UserData for MuxDomain { + fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_meta_method(mlua::MetaMethod::ToString, |_, this, _: ()| { + Ok(format!("MuxDomain(pane_id:{}, pid:{})", this.0, unsafe { + libc::getpid() + })) + }); + methods.add_method("domain_id", |_, this, _: ()| Ok(this.0)); + + methods.add_method("is_spawnable", |_, this, _: ()| { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + Ok(domain.spawnable()) + }); + + methods.add_async_method("attach", |_, this, window: Option| async move { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + domain.attach(window.map(|w| w.0)).await.map_err(|err| { + mlua::Error::external(format!( + "failed to attach domain {}: {err:#}", + domain.domain_name() + )) + }) + }); + + methods.add_method("detach", |_, this, _: ()| { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + domain.detach().map_err(|err| { + mlua::Error::external(format!( + "failed to detach domain {}: {err:#}", + domain.domain_name() + )) + }) + }); + + methods.add_method("state", |_, this, _: ()| { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + Ok(match domain.state() { + DomainState::Attached => "Attached", + DomainState::Detached => "Detached", + }) + }); + + methods.add_method("name", |_, this, _: ()| { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + Ok(domain.domain_name().to_string()) + }); + + methods.add_async_method("label", |_, this, _: ()| async move { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + Ok(domain.domain_label().await) + }); + + methods.add_method("has_any_panes", |_, this, _: ()| { + let mux = get_mux()?; + let domain = this.resolve(&mux)?; + let have_panes_in_domain = mux + .iter_panes() + .iter() + .any(|p| p.domain_id() == domain.domain_id()); + Ok(have_panes_in_domain) + }); + } +} diff --git a/lua-api-crates/mux/src/lib.rs b/lua-api-crates/mux/src/lib.rs index 3af9a3fa1..f3daaa0b2 100644 --- a/lua-api-crates/mux/src/lib.rs +++ b/lua-api-crates/mux/src/lib.rs @@ -2,7 +2,7 @@ use config::keyassignment::SpawnTabDomain; use config::lua::mlua::{self, Lua, UserData, UserDataMethods, Value as LuaValue}; use config::lua::{get_or_create_module, get_or_create_sub_module}; use luahelper::impl_lua_conversion_dynamic; -use mux::domain::SplitSource; +use mux::domain::{DomainId, SplitSource}; use mux::pane::{Pane, PaneId}; use mux::tab::{SplitDirection, SplitRequest, SplitSize, Tab, TabId}; use mux::window::{Window, WindowId}; @@ -13,10 +13,12 @@ use std::sync::Arc; use wezterm_dynamic::{FromDynamic, ToDynamic}; use wezterm_term::TerminalSize; +mod domain; mod pane; mod tab; mod window; +pub use domain::MuxDomain; pub use pane::MuxPane; pub use tab::MuxTab; pub use window::MuxWindow; @@ -107,6 +109,55 @@ pub fn register(lua: &Lua) -> anyhow::Result<()> { })?, )?; + mux_mod.set( + "get_domain", + lua.create_function(|_, domain: LuaValue| { + let mux = get_mux()?; + match domain { + LuaValue::Nil => Ok(Some(MuxDomain(mux.default_domain().domain_id()))), + LuaValue::String(s) => match s.to_str() { + Ok(name) => Ok(mux + .get_domain_by_name(name) + .map(|dom| MuxDomain(dom.domain_id()))), + Err(err) => Err(mlua::Error::external(format!( + "invalid domain identifier passed to mux.get_domain: {err:#}" + ))), + }, + LuaValue::Integer(id) => match TryInto::::try_into(id) { + Ok(id) => Ok(mux.get_domain(id).map(|dom| MuxDomain(dom.domain_id()))), + Err(err) => Err(mlua::Error::external(format!( + "invalid domain identifier passed to mux.get_domain: {err:#}" + ))), + }, + _ => Err(mlua::Error::external( + "invalid domain identifier passed to mux.get_domain".to_string(), + )), + } + })?, + )?; + + mux_mod.set( + "all_domains", + lua.create_function(|_, _: ()| { + let mux = get_mux()?; + Ok(mux + .iter_domains() + .into_iter() + .map(|dom| MuxDomain(dom.domain_id())) + .collect::>()) + })?, + )?; + + mux_mod.set( + "set_default_domain", + lua.create_function(|_, domain: MuxDomain| { + let mux = get_mux()?; + let domain = domain.resolve(&mux)?; + mux.set_default_domain(&domain); + Ok(()) + })?, + )?; + Ok(()) }