Nested session handling (#5417)

* fix: theme query and updated pixel dimensions

* add no-ui-fullscreen key/action/capability

* protocol core

* integration test harness

* fix integration tests

* fix flaky tests

* nested ui (dimming) and directional focus initial implementation

* integration tests for nested ui dimming and directional focus

* initial full screen nesteed implementation

* fix: keep hidden floating panes hidden across nested no-ui fullscreen

* fix integration tests

* no ui fullscreen integration tests

* hardening behavior/tests

* fix(nested): re-derive descend/dim on host tab switch and new-tab so clicking dimmed chrome ascends

* initial modal implementation

* initial ui improvements

* fix integration tests

* improve nested selection ui

* nested session modal refactoring and tests

* improve integration test speed somewhat

* some ui adjustments

* simplify full/auto nested session handling

* hints in compact-bar

* guest stdin leak fix

* allow floating panes to be fullscreen

* make nested session handling configurable

* protocol reannounce

* refactor: naming confusion

* improve ux somewhat

* rustfmt

* merge fixes

* improve integration test rate limiting (fix ci)

* add pr
This commit is contained in:
Aram Drevekenin
2026-07-27 18:24:37 +02:00
committed by GitHub
parent d0f829f3fd
commit ea07e2d5b6
169 changed files with 15120 additions and 486 deletions
+121 -56
View File
@@ -45,30 +45,14 @@ pub fn build(sh: &Shell, flags: flags::Build) -> anyhow::Result<()> {
crate::status(msg);
eprintln!("{}", msg);
let mut base_cmd = cmd!(sh, "{cargo} build --target wasm32-wasip1");
if flags.release {
base_cmd = base_cmd.arg("--release");
}
for member in &plugin_members {
let plugin_name = member
.crate_name
.rsplit_once('/')
.context("Cannot determine plugin name from crate path")?
.1;
base_cmd = base_cmd.args(["-p", plugin_name]);
}
base_cmd.run().context("failed to build plugins")?;
if flags.release {
for member in &plugin_members {
let plugin_name = member
.crate_name
.rsplit_once('/')
.context("Cannot determine plugin name from crate path")?
.1;
move_plugin_to_assets(sh, plugin_name)?;
}
build_plugins_release_into_assets(sh, &plugin_members)?;
} else {
let mut base_cmd = cmd!(sh, "{cargo} build --target wasm32-wasip1");
for member in &plugin_members {
base_cmd = base_cmd.args(["-p", plugin_name_of(member)?]);
}
base_cmd.run().context("failed to build plugins")?;
write_plugin_stamp(sh);
}
}
@@ -126,6 +110,113 @@ fn plugins_force() -> bool {
std::env::var_os("ZELLIJ_FORCE_PLUGINS").is_some()
}
fn plugin_asset_path(plugin_name: &str) -> PathBuf {
crate::asset_dir()
.join("plugins")
.join(plugin_name)
.with_extension("wasm")
}
fn newest_plugin_source_time() -> Option<std::time::SystemTime> {
let root = crate::project_root();
["default-plugins", "zellij-tile", "zellij-tile-utils"]
.iter()
.filter_map(|dir| newest_file_time(&root.join(dir)))
.max()
}
fn newest_file_time(dir: &Path) -> Option<std::time::SystemTime> {
let entries = std::fs::read_dir(dir).ok()?;
let mut newest: Option<std::time::SystemTime> = None;
for entry in entries.flatten() {
let path = entry.path();
let file_type = match entry.file_type() {
Ok(file_type) => file_type,
Err(_) => continue,
};
if file_type.is_dir() {
if path
.file_name()
.map(|name| name == "target")
.unwrap_or(false)
{
continue;
}
if let Some(child) = newest_file_time(&path) {
newest = Some(newest.map_or(child, |current| current.max(child)));
}
} else if let Ok(modified) = entry.metadata().and_then(|m| m.modified()) {
newest = Some(newest.map_or(modified, |current| current.max(modified)));
}
}
newest
}
pub fn ensure_plugin_assets(sh: &Shell) -> anyhow::Result<()> {
let plugin_members: Vec<&WorkspaceMember> = crate::workspace_members()
.iter()
.filter(|m| m.build && m.crate_name.contains("plugins"))
.collect();
if plugin_members.is_empty() {
return Ok(());
}
let newest_source = newest_plugin_source_time();
let stale = plugins_force()
|| plugin_members.iter().any(|member| {
let plugin_name = match member.crate_name.rsplit_once('/') {
Some((_, name)) => name,
None => return true,
};
let asset_time = std::fs::metadata(plugin_asset_path(plugin_name))
.and_then(|m| m.modified())
.ok();
match (asset_time, newest_source) {
(Some(asset_time), Some(newest_source)) => asset_time < newest_source,
_ => true,
}
});
if !stale {
let msg = ">> Plugin assets up to date, skipping plugin build";
crate::status(msg);
eprintln!("{}", msg);
return Ok(());
}
let msg = ">> Building plugin assets (release)";
crate::status(msg);
eprintln!("{}", msg);
build_plugins_release_into_assets(sh, &plugin_members)
}
fn build_plugins_release_into_assets(
sh: &Shell,
plugin_members: &[&WorkspaceMember],
) -> anyhow::Result<()> {
let cargo = crate::cargo()?;
let mut base_cmd = cmd!(sh, "{cargo} build --target wasm32-wasip1 --release");
for member in plugin_members {
let plugin_name = plugin_name_of(member)?;
base_cmd = base_cmd.args(["-p", plugin_name]);
}
base_cmd.run().context("failed to build plugin assets")?;
for member in plugin_members {
move_plugin_to_assets(sh, plugin_name_of(member)?)?;
}
Ok(())
}
fn plugin_name_of(member: &WorkspaceMember) -> anyhow::Result<&'static str> {
Ok(member
.crate_name
.rsplit_once('/')
.context("Cannot determine plugin name from crate path")?
.1)
}
fn plugin_stamp_path() -> PathBuf {
PathBuf::from(
std::env::var_os("CARGO_TARGET_DIR")
@@ -143,41 +234,10 @@ fn plugin_sources_changed() -> bool {
Ok(stamp_time) => stamp_time,
Err(_) => return true,
};
let root = crate::project_root();
["default-plugins", "zellij-tile", "zellij-tile-utils"]
.iter()
.any(|dir| dir_changed_since(&root.join(dir), stamp_time))
}
fn dir_changed_since(dir: &Path, since: std::time::SystemTime) -> bool {
let entries = match std::fs::read_dir(dir) {
Ok(entries) => entries,
Err(_) => return false,
};
for entry in entries.flatten() {
let file_type = match entry.file_type() {
Ok(file_type) => file_type,
Err(_) => continue,
};
let path = entry.path();
if file_type.is_dir() {
if path
.file_name()
.map(|name| name == "target")
.unwrap_or(false)
{
continue;
}
if dir_changed_since(&path, since) {
return true;
}
} else if let Ok(modified) = entry.metadata().and_then(|m| m.modified()) {
if modified > since {
return true;
}
}
match newest_plugin_source_time() {
Some(newest_source) => newest_source > stamp_time,
None => true,
}
false
}
pub fn proto(sh: &Shell) -> anyhow::Result<()> {
@@ -205,6 +265,11 @@ fn run_proto_codegen(sh: &Shell, force: bool) {
"src/web_server_contract",
"generated_web_server_api.rs",
),
(
"assets/prost_nested_session",
"src/nested_session_contract",
"generated_nested_session_api.rs",
),
];
for (out_subdir, src_subdir, include_file) in specs {
+2
View File
@@ -12,6 +12,8 @@ pub fn integration_test(sh: &Shell, flags: flags::IntegrationTest) -> anyhow::Re
crate::status(msg);
println!("{}", msg);
crate::build::ensure_plugin_assets(sh).context(err_context)?;
let profile = if flags.no_opt { "dev" } else { "dev-opt" };
let nextest_available = cmd!(sh, "{cargo} nextest --version")