use std::io;
use crate::format::expand_format_for_window;
use crate::types::{AppState, Node, 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
}
pub(crate) fn propagate_osc_titles(app: &mut AppState) -> bool {
let allow_set_title = app.allow_set_title;
if !allow_set_title {
return false;
}
let mut dirty = false;
for win in app.windows.iter_mut() {
propagate_osc_titles_in_tree(&mut win.root, &mut dirty);
}
dirty
}
pub(crate) fn active_pane_progress(app: &AppState) -> Option<(u8, u8)> {
let win = app.windows.get(app.active_idx)?;
let pane = crate::tree::active_pane(&win.root, &win.active_path)?;
if pane.dead {
return None;
}
let parser = pane.term.lock().ok()?;
parser.screen().progress()
}
pub(crate) fn take_pane_clipboard(app: &AppState) -> Option<(Vec<u8>, Vec<u8>)> {
for win in &app.windows {
if let Some(payload) = drain_clipboard_in_node(&win.root) {
return Some(payload);
}
}
None
}
fn drain_clipboard_in_node(node: &Node) -> Option<(Vec<u8>, Vec<u8>)> {
match node {
Node::Leaf(p) => {
if p.dead {
return None;
}
let mut parser = p.term.lock().ok()?;
parser.screen_mut().take_clipboard()
}
Node::Split { children, .. } => {
for c in children {
if let Some(r) = drain_clipboard_in_node(c) {
return Some(r);
}
}
None
}
}
}
fn propagate_osc_titles_in_tree(node: &mut Node, dirty: &mut bool) {
match node {
Node::Leaf(p) => {
if p.dead || p.title_locked {
return;
}
if let Ok(parser) = p.term.lock() {
let osc = parser.screen().title();
if !osc.is_empty() {
let osc_owned = osc.to_string();
drop(parser);
if p.title != osc_owned {
p.title = osc_owned;
*dirty = true;
}
}
}
}
Node::Split { children, .. } => {
for c in children {
propagate_osc_titles_in_tree(c, dirty);
}
}
}
}
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) fn drain_cpr_pending(node: &mut crate::types::Node) {
use std::io::Write as _;
match node {
crate::types::Node::Leaf(p) => {
if p.cpr_pending
.swap(false, std::sync::atomic::Ordering::AcqRel)
{
let (r, c) = p
.term
.lock()
.map(|g| g.screen().cursor_position())
.unwrap_or((0, 0));
let response = format!("\x1b[{};{}R", r + 1, c + 1);
let _ = p.writer.write_all(response.as_bytes());
let _ = p.writer.flush();
}
}
crate::types::Node::Split { children, .. } => {
for c in children {
drain_cpr_pending(c);
}
}
}
}
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)",
];