use std::io;
use crate::types::{AppState, Node, Window};
use crate::format::expand_format_for_window;
use crate::util::WinInfo;
pub(crate) fn collect_pane_paths_server(node: &Node, path: &mut Vec<usize>, panes: &mut Vec<Vec<usize>>) {
match node {
Node::Leaf(_) => { panes.push(path.clone()); }
Node::Split { children, .. } => {
for (i, c) in children.iter().enumerate() {
path.push(i);
collect_pane_paths_server(c, path, panes);
path.pop();
}
}
}
}
pub(crate) fn serialize_bindings_json(app: &AppState) -> String {
use crate::commands::format_action;
use crate::config::format_key_binding;
let mut out = String::from("[");
let mut first = true;
for (table_name, binds) in &app.key_tables {
for bind in binds {
if !first { out.push(','); }
first = false;
let key_str = json_escape_string(&format_key_binding(&bind.key));
let cmd_str = json_escape_string(&format_action(&bind.action));
let tbl_str = json_escape_string(table_name);
out.push_str(&format!("{{\"t\":\"{}\",\"k\":\"{}\",\"c\":\"{}\",\"r\":{}}}",
tbl_str, key_str, cmd_str, bind.repeat));
}
}
out.push(']');
out
}
pub(crate) fn json_escape_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 8);
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'"' => out.push_str("\\\""),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out
}
pub(crate) fn list_windows_json_with_tabs(app: &AppState) -> io::Result<String> {
let mut v: Vec<WinInfo> = Vec::new();
for (i, w) in app.windows.iter().enumerate() {
let is_active = i == app.active_idx;
let fmt = if is_active { &app.window_status_current_format } else { &app.window_status_format };
let tab = expand_format_for_window(fmt, app, i);
v.push(WinInfo {
id: w.id,
name: w.name.clone(),
active: is_active,
activity: w.activity_flag,
tab_text: tab,
});
}
serde_json::to_string(&v).map_err(|e| io::Error::new(io::ErrorKind::Other, format!("json error: {e}")))
}
pub(crate) fn combined_data_version(app: &AppState) -> u64 {
let mut v = 0u64;
fn walk(node: &Node, v: &mut u64) {
match node {
Node::Leaf(p) => { *v = v.wrapping_add(p.data_version.load(std::sync::atomic::Ordering::Acquire)); }
Node::Split { children, .. } => { for c in children { walk(c, v); } }
}
}
if let Some(win) = app.windows.get(app.active_idx) {
walk(&win.root, &mut v);
}
let mode_tag: u64 = match &app.mode {
crate::types::Mode::Passthrough => 0,
crate::types::Mode::Prefix { .. } => 1,
crate::types::Mode::CopyMode => 2,
crate::types::Mode::CopySearch { .. } => 3,
crate::types::Mode::ClockMode => 4,
crate::types::Mode::PopupMode { .. } => 5,
crate::types::Mode::ConfirmMode { .. } => 6,
crate::types::Mode::MenuMode { .. } => 7,
crate::types::Mode::PaneChooser { .. } => 8,
crate::types::Mode::BufferChooser { .. } => 9,
_ => 10,
};
v = v.wrapping_add(mode_tag.wrapping_mul(0x1_0000_0000));
for (wi, w) in app.windows.iter().enumerate() {
if w.zoom_saved.is_some() {
v = v.wrapping_add(0x8000_0000_0000_u64.wrapping_add(wi as u64));
}
}
if app.client_prefix_active {
v = v.wrapping_add(0x4000_0000_0000);
}
if let Some((r, c)) = app.copy_pos {
v = v.wrapping_add((r as u64).wrapping_mul(0x10001).wrapping_add(c as u64));
}
v = v.wrapping_add((app.copy_scroll_offset as u64).wrapping_mul(0x20003));
if let Some((ar, ac)) = app.copy_anchor {
v = v.wrapping_add((ar as u64).wrapping_mul(0x30007).wrapping_add(ac as u64));
}
v
}
pub(crate) fn window_data_version(win: &Window) -> u64 {
let mut v = 0u64;
fn walk(node: &Node, v: &mut u64) {
match node {
Node::Leaf(p) => { *v = v.wrapping_add(p.data_version.load(std::sync::atomic::Ordering::Acquire)); }
Node::Split { children, .. } => { for c in children { walk(c, v); } }
}
}
walk(&win.root, &mut v);
v
}
pub(crate) fn check_window_activity(app: &mut AppState) -> Vec<&'static str> {
let active = app.active_idx;
let monitor_silence_secs = app.monitor_silence;
let bell_action = app.bell_action.clone();
let mut triggered_hooks: Vec<&'static str> = Vec::new();
let mut forward_bell = false;
for (i, win) in app.windows.iter_mut().enumerate() {
let has_bell = check_pane_bells(&win.root);
if has_bell && i != active {
match bell_action.as_str() {
"any" | "other" => {
if !win.bell_flag { win.bell_flag = true; triggered_hooks.push("alert-bell"); }
forward_bell = true;
}
_ => {} }
} else if has_bell && i == active {
match bell_action.as_str() {
"any" | "current" => {
if !win.bell_flag { win.bell_flag = true; triggered_hooks.push("alert-bell"); }
forward_bell = true;
}
_ => {}
}
}
if i == active {
win.activity_flag = false;
win.bell_flag = false;
win.silence_flag = false;
win.last_seen_version = window_data_version(win);
let cur = window_data_version(win);
if cur != win.last_seen_version {
win.last_output_time = std::time::Instant::now();
}
continue;
}
let cur = window_data_version(win);
if cur != win.last_seen_version {
if app.monitor_activity && !win.activity_flag {
win.activity_flag = true;
triggered_hooks.push("alert-activity");
}
win.last_output_time = std::time::Instant::now();
win.silence_flag = false; win.last_seen_version = cur;
}
if monitor_silence_secs > 0 {
let elapsed = win.last_output_time.elapsed().as_secs();
if elapsed >= monitor_silence_secs && !win.silence_flag {
win.silence_flag = true;
triggered_hooks.push("alert-silence");
}
}
}
if forward_bell {
app.bell_forward = true;
}
triggered_hooks
}
fn check_pane_bells(node: &Node) -> bool {
match node {
Node::Leaf(p) => {
p.bell_pending.swap(false, std::sync::atomic::Ordering::AcqRel)
}
Node::Split { children, .. } => {
let mut any = false;
for c in children {
if check_pane_bells(c) { any = true; }
}
any
}
}
}
pub(crate) const TMUX_COMMANDS: &[&str] = &[
"attach-session (attach)", "bind-key (bind)", "break-pane (breakp)",
"capture-pane (capturep)", "choose-buffer (chooseb)", "choose-client",
"choose-session", "choose-tree", "choose-window",
"clear-history (clearhist)", "clear-prompt-history (clearphist)",
"clock-mode", "command-prompt",
"confirm-before (confirm)", "copy-mode", "customize-mode",
"delete-buffer (deleteb)", "detach-client (detach)",
"display-menu (menu)", "display-message (display)",
"display-panes (displayp)", "display-popup (popup)",
"find-window (findw)", "has-session (has)",
"if-shell (if)", "join-pane (joinp)",
"kill-pane (killp)", "kill-server", "kill-session",
"kill-window (killw)", "last-pane (lastp)", "last-window (last)",
"link-window (linkw)", "list-buffers (lsb)", "list-clients (lsc)",
"list-commands (lscm)", "list-keys (lsk)", "list-panes (lsp)",
"list-sessions (ls)", "list-windows (lsw)",
"load-buffer (loadb)", "lock-client (lockc)",
"lock-server (lock)", "lock-session (locks)",
"move-pane (movep)", "move-window (movew)",
"new-session (new)", "new-window (neww)",
"next-layout (nextl)", "next-window (next)",
"paste-buffer (pasteb)", "pipe-pane (pipep)",
"previous-layout (prevl)", "previous-window (prev)",
"refresh-client (refresh)", "rename-session (rename)",
"rename-window (renamew)", "resize-pane (resizep)",
"resize-window (resizew)", "respawn-pane (respawnp)",
"respawn-window (respawnw)", "rotate-window (rotatew)",
"run-shell (run)", "save-buffer (saveb)",
"select-layout (selectl)", "select-pane (selectp)",
"select-window (selectw)", "send-keys (send)",
"send-prefix", "server-info (info)",
"set-buffer (setb)", "set-environment (setenv)",
"set-hook", "set-option (set)",
"set-window-option (setw)", "show-buffer (showb)",
"show-environment (showenv)", "show-hooks",
"show-messages (showmsgs)", "show-options (show)",
"show-prompt-history (showphist)", "show-window-options (showw)",
"source-file (source)",
"split-window (splitw)", "start-server (start)",
"suspend-client (suspendc)", "swap-pane (swapp)",
"swap-window (swapw)", "switch-client (switchc)",
"unbind-key (unbind)", "unlink-window (unlinkw)",
"wait-for (wait)",
];