use super::tmux_query::ztmux_cmd;
const PRESETS: &[(&str, &str)] = &[
("even-h", "built-in even-horizontal"),
("even-v", "built-in even-vertical"),
("main-h", "built-in main-horizontal"),
("main-v", "built-in main-vertical"),
("tiled", "built-in tiled"),
("dev", "editor (65%) + side terminal (35%)"),
("ide", "editor + two stacked side panes"),
("grid", "four tiled panes"),
];
pub(crate) fn run(socket: &str) -> i32 {
let args: Vec<String> = std::env::args().collect();
let name = args.iter().skip_while(|a| a.as_str() != "layout").nth(1);
let name = match name {
Some(n) if !n.starts_with('-') => n.as_str(),
_ => {
eprintln!("usage: ztmux layout <name> [-t window] [-f] | ztmux layout list");
return 2;
}
};
if name == "list" {
for (n, d) in PRESETS {
println!("{n:<8} {d}");
}
return 0;
}
let target = args
.iter()
.position(|a| a == "-t")
.and_then(|i| args.get(i + 1))
.cloned();
let apply = args.iter().any(|a| a == "-f" || a == "--apply");
let Some(cmds) = preset_cmds(name, target.as_deref()) else {
eprintln!("ztmux layout: unknown preset '{name}' (try `ztmux layout list`)");
return 1;
};
if !apply {
println!("# would apply preset '{name}' ({} commands):", cmds.len());
for c in &cmds {
println!("ztmux {}", c.join(" "));
}
println!("# re-run with -f/--apply to run them");
return 0;
}
for c in &cmds {
let argv: Vec<&str> = c.iter().map(String::as_str).collect();
match ztmux_cmd(socket, &argv).output() {
Ok(o) if o.status.success() => {}
Ok(o) => {
eprintln!(
"ztmux layout: `{}` failed: {}",
c.join(" "),
String::from_utf8_lossy(&o.stderr).trim()
);
return 1;
}
Err(e) => {
eprintln!("ztmux layout: `{}` failed: {e}", c.join(" "));
return 1;
}
}
}
0
}
fn preset_cmds(name: &str, target: Option<&str>) -> Option<Vec<Vec<String>>> {
let s = |parts: &[&str]| {
parts
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
};
let builtin = |layout: &str| vec![s(&["select-layout", layout])];
let base: Vec<Vec<String>> = match name {
"even-h" => builtin("even-horizontal"),
"even-v" => builtin("even-vertical"),
"main-h" => builtin("main-horizontal"),
"main-v" => builtin("main-vertical"),
"tiled" => builtin("tiled"),
"dev" => vec![s(&["split-window", "-h", "-l", "35%"])],
"ide" => vec![
s(&["split-window", "-h", "-l", "35%"]),
s(&["split-window", "-v", "-l", "50%"]),
s(&["select-pane", "-L"]),
],
"grid" => vec![
s(&["split-window", "-h"]),
s(&["split-window", "-v"]),
s(&["select-pane", "-L"]),
s(&["split-window", "-v"]),
s(&["select-layout", "tiled"]),
],
_ => return None,
};
let cmds = match target {
None => base,
Some(t) => base
.into_iter()
.map(|mut c| {
c.push("-t".to_string());
c.push(t.to_string());
c
})
.collect(),
};
Some(cmds)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtin_preset_is_a_single_select_layout() {
let cmds = preset_cmds("main-v", None).unwrap();
assert_eq!(cmds.len(), 1);
assert_eq!(cmds[0], vec!["select-layout", "main-vertical"]);
}
#[test]
fn composite_preset_creates_panes() {
let cmds = preset_cmds("ide", None).unwrap();
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0][0], "split-window");
assert!(cmds.iter().any(|c| c[0] == "select-pane"));
}
#[test]
fn target_is_threaded_onto_every_command() {
let cmds = preset_cmds("dev", Some("work:1")).unwrap();
assert!(cmds.iter().all(|c| c.contains(&"-t".to_string())));
assert!(cmds[0].ends_with(&["-t".to_string(), "work:1".to_string()]));
}
#[test]
fn unknown_preset_is_none() {
assert!(preset_cmds("nope", None).is_none());
}
#[test]
fn all_builtins_map_to_select_layout() {
for (name, layout) in [
("even-h", "even-horizontal"),
("even-v", "even-vertical"),
("main-h", "main-horizontal"),
("main-v", "main-vertical"),
("tiled", "tiled"),
] {
let cmds = preset_cmds(name, None).unwrap();
assert_eq!(
cmds,
vec![vec!["select-layout".to_string(), layout.to_string()]]
);
}
}
#[test]
fn grid_preset_tiles_four_panes() {
let cmds = preset_cmds("grid", None).unwrap();
assert_eq!(cmds.len(), 5);
assert_eq!(cmds.iter().filter(|c| c[0] == "split-window").count(), 3);
assert_eq!(*cmds.last().unwrap(), vec!["select-layout", "tiled"]);
}
#[test]
fn dev_preset_is_a_single_horizontal_split() {
let cmds = preset_cmds("dev", None).unwrap();
assert_eq!(cmds, vec![vec!["split-window", "-h", "-l", "35%"]]);
}
#[test]
fn every_listed_preset_expands() {
for (name, _) in PRESETS {
assert!(preset_cmds(name, None).is_some(), "{name} should expand");
}
}
}