#![cfg(unix)]
use std::error::Error;
mod common;
use common::{send_request, session_name, start_server, TestHarness};
use rmux_proto::{
DisplayMessageRequest, ListWindowsRequest, NewSessionRequest, NewWindowRequest, Request,
Response, Target, TerminalSize,
};
fn default_shell_window_name() -> String {
"bash".to_owned()
}
fn assert_window_format_line(
line: &str,
window_index: &str,
window_name: &str,
raw_flags: &str,
active: &str,
last: &str,
conditional: &str,
) {
let fields: Vec<&str> = line.split(':').collect();
assert_eq!(fields.len(), 14, "unexpected format fields: {fields:?}");
assert_eq!(fields[0], "alpha");
assert_eq!(fields[1], "2");
assert_eq!(fields[2], "0");
assert_eq!(fields[3], "x");
assert_eq!(fields[4], window_index);
assert_eq!(fields[5], window_name);
assert_eq!(fields[6], raw_flags);
assert_eq!(fields[7], active);
assert_eq!(fields[8], last);
assert_eq!(fields[9], format!("@{window_index}"));
assert_eq!(fields[10], "");
assert_eq!(fields[11], format!("{window_index}{window_name}alpha"));
assert!(
!fields[12].contains("#{"),
"pane title should be resolved, not leaked as a format token"
);
assert_eq!(fields[13], conditional);
}
fn is_unix_pty_path(path: &str) -> bool {
path.starts_with("/dev/pts/") || path.starts_with("/dev/ttys")
}
#[tokio::test]
async fn list_windows_uses_shared_formatter_through_real_socket() -> Result<(), Box<dyn Error>> {
let harness = TestHarness::new("formats-list-windows");
let handle = start_server(&harness).await?;
let alpha = session_name("alpha");
let created = send_request(
harness.socket_path(),
&Request::NewSession(NewSessionRequest {
session_name: alpha.clone(),
detached: true,
size: Some(TerminalSize {
cols: 120,
rows: 40,
}),
environment: Some(vec![
"SHELL=/bin/bash".to_owned(),
"TERM_PROGRAM=tmux".to_owned(),
]),
}),
)
.await?;
assert!(matches!(created, Response::NewSession(_)));
let new_window = send_request(
harness.socket_path(),
&Request::NewWindow(NewWindowRequest {
target: alpha.clone(),
name: Some("logs".to_owned()),
detached: false,
start_directory: None,
environment: None,
command: None,
target_window_index: None,
insert_at_target: false,
}),
)
.await?;
assert!(matches!(new_window, Response::NewWindow(_)));
let listed = send_request(
harness.socket_path(),
&Request::ListWindows(ListWindowsRequest {
target: alpha,
format: Some(
"#{session_name}:#{session_windows}:#{session_attached}:#{session_width}x#{session_height}:#{window_index}:#{window_name}:#{window_raw_flags}:#{window_active}:#{window_last_flag}:#{window_id}:#{missing}:#I#W#S:#{=21:pane_title}:#{?window_active,yes,no}"
.to_owned(),
),
}),
)
.await?;
let output = listed
.command_output()
.expect("list-windows returns command output");
let stdout = std::str::from_utf8(output.stdout()).expect("list-windows output is utf-8");
let lines: Vec<&str> = stdout.lines().collect();
assert_eq!(lines.len(), 2, "unexpected list-windows output: {stdout:?}");
assert_window_format_line(
lines[0].trim_end(),
"0",
&default_shell_window_name(),
"-",
"0",
"1",
"no",
);
assert_window_format_line(lines[1].trim_end(), "1", "logs", "*", "1", "0", "yes");
handle.shutdown().await?;
Ok(())
}
#[tokio::test]
async fn nested_conditionals_expand_inner_templates_through_real_socket(
) -> Result<(), Box<dyn Error>> {
let harness = TestHarness::new("formats-nested-conditionals");
let handle = start_server(&harness).await?;
let alpha = session_name("alpha");
let created = send_request(
harness.socket_path(),
&Request::NewSession(NewSessionRequest {
session_name: alpha.clone(),
detached: true,
size: Some(TerminalSize {
cols: 120,
rows: 40,
}),
environment: None,
}),
)
.await?;
assert!(matches!(created, Response::NewSession(_)));
let new_window = send_request(
harness.socket_path(),
&Request::NewWindow(NewWindowRequest {
target: alpha.clone(),
name: Some("logs".to_owned()),
detached: false,
start_directory: None,
environment: None,
command: None,
target_window_index: None,
insert_at_target: false,
}),
)
.await?;
assert!(matches!(new_window, Response::NewWindow(_)));
let listed = send_request(
harness.socket_path(),
&Request::ListWindows(ListWindowsRequest {
target: alpha,
format: Some(
"#{?window_active,#{window_name},#{?window_last_flag,last,#{session_name}}}"
.to_owned(),
),
}),
)
.await?;
let output = listed
.command_output()
.expect("list-windows returns command output");
assert_eq!(
std::str::from_utf8(output.stdout()).expect("list-windows output is utf-8"),
"last\nlogs\n"
);
handle.shutdown().await?;
Ok(())
}
#[tokio::test]
async fn display_message_session_target_includes_active_pane_runtime_context(
) -> Result<(), Box<dyn Error>> {
let harness = TestHarness::new("formats-display-session-pane-context");
let handle = start_server(&harness).await?;
let alpha = session_name("alpha");
let created = send_request(
harness.socket_path(),
&Request::NewSession(NewSessionRequest {
session_name: alpha.clone(),
detached: true,
size: Some(TerminalSize { cols: 80, rows: 24 }),
environment: None,
}),
)
.await?;
assert!(matches!(created, Response::NewSession(_)));
let displayed = send_request(
harness.socket_path(),
&Request::DisplayMessage(DisplayMessageRequest {
target: Some(Target::Session(alpha)),
print: true,
message: Some(
"#{session_name}|#{window_index}|#{pane_index}|#{pane_current_path}|#{pane_pid}|#{pane_tty}|#{socket_path}"
.to_owned(),
),
}),
)
.await?;
let output = displayed
.command_output()
.expect("display-message -p returns command output");
let stdout = std::str::from_utf8(output.stdout()).expect("display-message output is utf-8");
let fields: Vec<&str> = stdout.trim_end().split('|').collect();
assert_eq!(fields.len(), 7);
assert_eq!(fields[0], "alpha");
assert_eq!(fields[1], "0");
assert_eq!(fields[2], "0");
assert!(!fields[3].is_empty(), "pane_current_path must be populated");
assert!(fields[4].parse::<u32>().is_ok(), "pane_pid must be numeric");
assert!(is_unix_pty_path(fields[5]), "pane_tty must be a pty");
assert_eq!(fields[6], harness.socket_path().to_string_lossy());
handle.shutdown().await?;
Ok(())
}