use super::tmux_query::query_lines;
const MODAL_OPT: &str = "@ztmux-modal";
const HINT_SAVE: &str = "@ztmux-modal-saved-hint";
const ENTRY_KEYS: &[&str] = &["C-p", "C-t", "C-n", "C-s", "C-o", "C-g"];
const CONFIG: &str = r#"# ztmux zellij-style modal keybindings (ztmux modal on)
# --- Pane mode (Ctrl-p) ---
bind -T pane -N 'move left' h { select-pane -L ; switch-client -T pane }
bind -T pane -N 'move down' j { select-pane -D ; switch-client -T pane }
bind -T pane -N 'move up' k { select-pane -U ; switch-client -T pane }
bind -T pane -N 'move right' l { select-pane -R ; switch-client -T pane }
bind -T pane -N 'new pane' n { split-window ; switch-client -T root }
bind -T pane -N 'split right' d { split-window -h ; switch-client -T root }
bind -T pane -N 'close pane' x { kill-pane }
bind -T pane -N 'fullscreen' z { resize-pane -Z ; switch-client -T pane }
bind -T pane -N 'rename pane' r { command-prompt -p 'pane name:' 'select-pane -T "%%"' }
bind -T pane -N 'exit' Enter { switch-client -T root }
bind -T pane -N 'exit' Escape { switch-client -T root }
bind -T pane -N 'exit' q { switch-client -T root }
# --- Tab (window) mode (Ctrl-t) ---
bind -T tab -N 'previous' h { previous-window ; switch-client -T tab }
bind -T tab -N 'next' l { next-window ; switch-client -T tab }
bind -T tab -N 'new tab' n { new-window }
bind -T tab -N 'close tab' x { kill-window }
bind -T tab -N 'rename tab' r { command-prompt -p 'tab name:' 'rename-window "%%"' }
bind -T tab -N 'exit' Enter { switch-client -T root }
bind -T tab -N 'exit' Escape { switch-client -T root }
bind -T tab -N 'exit' q { switch-client -T root }
# --- Resize mode (Ctrl-n) ---
bind -T resize -N 'left' h { resize-pane -L 5 ; switch-client -T resize }
bind -T resize -N 'down' j { resize-pane -D 5 ; switch-client -T resize }
bind -T resize -N 'up' k { resize-pane -U 5 ; switch-client -T resize }
bind -T resize -N 'right' l { resize-pane -R 5 ; switch-client -T resize }
bind -T resize -N 'exit' Enter { switch-client -T root }
bind -T resize -N 'exit' Escape { switch-client -T root }
bind -T resize -N 'exit' q { switch-client -T root }
# --- Session mode (Ctrl-o) ---
bind -T session -N 'session manager' w { display-popup -E -w 80% -h 70% 'ztmux -S "${TMUX%%,*}" sessions' }
bind -T session -N 'detach' d { detach-client }
bind -T session -N 'exit' Enter { switch-client -T root }
bind -T session -N 'exit' Escape { switch-client -T root }
bind -T session -N 'exit' q { switch-client -T root }
# --- Locked mode (Ctrl-g) — every key passes through until Ctrl-g ---
bind -T locked -N 'unlock' C-g { switch-client -T root }
# --- Entry keys (root table, no prefix) ---
bind -n -N 'ztmux: pane mode' C-p { switch-client -T pane }
bind -n -N 'ztmux: tab mode' C-t { switch-client -T tab }
bind -n -N 'ztmux: resize mode' C-n { switch-client -T resize }
bind -n -N 'ztmux: scroll mode' C-s { copy-mode }
bind -n -N 'ztmux: session mode' C-o { switch-client -T session }
bind -n -N 'ztmux: lock' C-g { switch-client -T locked }
"#;
pub(crate) fn run(socket: &str) -> i32 {
match op_arg().as_deref() {
Some("on") => set_modal(socket, true),
Some("off") => set_modal(socket, false),
_ => set_modal(socket, !is_on(socket)),
}
0
}
fn op_arg() -> Option<String> {
let args: Vec<String> = std::env::args().collect();
let i = args.iter().position(|a| a == "modal")?;
args.get(i + 1).filter(|s| !s.starts_with('-')).cloned()
}
fn is_on(socket: &str) -> bool {
query_lines(socket, &["show-options", "-gqv", MODAL_OPT])
.first()
.is_some_and(|v| v.trim() == "1")
}
fn set_modal(socket: &str, on: bool) {
if on {
let path = std::env::temp_dir().join(format!("ztmux-modal-{}.conf", std::process::id()));
if std::fs::write(&path, CONFIG).is_err() {
return;
}
let p = path.to_string_lossy().to_string();
let _ = query_lines(socket, &["source-file", &p]);
let _ = std::fs::remove_file(&path);
let _ = query_lines(
socket,
&[
"set-option",
"-gF",
HINT_SAVE,
"#{@ztmux-hint}",
";",
"set-option",
"-g",
"@ztmux-hint",
"on",
";",
"set-option",
"-g",
MODAL_OPT,
"1",
],
);
} else {
let mut argv: Vec<&str> = Vec::new();
for (i, k) in ENTRY_KEYS.iter().enumerate() {
if i != 0 {
argv.push(";");
}
argv.extend(["unbind-key", "-n", k]);
}
argv.extend([
";",
"set-option",
"-gF",
"@ztmux-hint",
"#{@ztmux-modal-saved-hint}",
";",
"set-option",
"-gu",
HINT_SAVE,
";",
"set-option",
"-gu",
MODAL_OPT,
]);
let _ = query_lines(socket, &argv);
}
}