rmux 0.1.1

A local terminal multiplexer with a tmux-style CLI, daemon runtime, Rust SDK, and ratatui integration.
#![cfg(unix)]

mod common;

use std::error::Error;
use std::time::Duration;

use common::{
    assert_success, stderr, stdout, terminate_child, CliHarness, FrozenTmuxBinary,
    TmuxCompatHarness, TmuxCompatRun, TmuxCompatRunConfig, FROZEN_TMUX_ENV,
};

#[test]
fn capture_pane_prints_unattached_transcript() -> Result<(), Box<dyn Error>> {
    let harness = CliHarness::new("capture-print")?;
    let mut daemon = harness.start_hidden_daemon()?;
    let marker = "cli_capture_print_marker";

    assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
    assert_success(&harness.run(&[
        "send-keys",
        "-t",
        "alpha:0.0",
        &format!("printf '{marker}\\n'"),
        "Enter",
    ])?);

    let output = wait_for_capture(&harness, marker)?;
    assert!(stdout(&output).contains(marker));
    assert!(stderr(&output).is_empty());

    terminate_child(daemon.child_mut())?;
    Ok(())
}

#[test]
fn capture_pane_writes_buffer_without_printing() -> Result<(), Box<dyn Error>> {
    let harness = CliHarness::new("capture-buffer")?;
    let mut daemon = harness.start_hidden_daemon()?;
    let marker = "cli_capture_buffer_marker";

    assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
    assert_success(&harness.run(&[
        "send-keys",
        "-t",
        "alpha:0.0",
        &format!("printf '{marker}\\n'"),
        "Enter",
    ])?);
    let _ = wait_for_capture(&harness, marker)?;

    assert_success(&harness.run(&["capture-pane", "-t", "alpha:0.0", "-b", "cap"])?);

    let show = harness.run(&["show-buffer", "-b", "cap"])?;
    assert_eq!(show.status.code(), Some(0));
    assert!(stdout(&show).contains(marker));
    assert!(stderr(&show).is_empty());

    terminate_child(daemon.child_mut())?;
    Ok(())
}

#[test]
fn capture_pane_print_does_not_reorder_buffers() -> Result<(), Box<dyn Error>> {
    let harness = CliHarness::new("capture-print-buffer-order")?;
    let mut daemon = harness.start_hidden_daemon()?;
    let marker = "cli_capture_order_marker";

    assert_success(&harness.run(&["new-session", "-d", "-s", "alpha"])?);
    assert_success(&harness.run(&["set-buffer", "stable-head"])?);
    assert_success(&harness.run(&[
        "send-keys",
        "-t",
        "alpha:0.0",
        &format!("printf '{marker}\\n'"),
        "Enter",
    ])?);
    let _ = wait_for_capture(&harness, marker)?;

    let show = harness.run(&["show-buffer"])?;
    assert_eq!(show.status.code(), Some(0));
    assert_eq!(stdout(&show), "stable-head");

    terminate_child(daemon.child_mut())?;
    Ok(())
}

#[test]
fn capture_pane_matches_frozen_tmux_for_respawned_output_when_available(
) -> Result<(), Box<dyn Error>> {
    let harness = TmuxCompatHarness::new("capture-pane-tmux-compat")?;
    let tmux_binary = match FrozenTmuxBinary::discover() {
        FrozenTmuxBinary::Available(path) => path,
        FrozenTmuxBinary::Unavailable {
            checked_path,
            reason,
        } => {
            eprintln!(
                "runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
                checked_path.display()
            );
            return Ok(());
        }
    };
    let config = TmuxCompatRunConfig::default();

    let create = harness.run_pair_with(
        &tmux_binary,
        &["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&create);
    let remain_on_exit = harness.run_pair_with(
        &tmux_binary,
        &["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&remain_on_exit);

    let respawn = harness.run_pair_with(
        &tmux_binary,
        &[
            "respawn-pane",
            "-k",
            "-t",
            "alpha:0.0",
            "printf 'capture-tmux-compat-marker\\n'; sleep 10",
        ],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&respawn);

    assert_capture_tmux_compat_when(
        &harness,
        &tmux_binary,
        &config,
        &["capture-pane", "-p", "-t", "alpha:0.0"],
        |run| {
            run.tmux
                .stdout_string()
                .contains("capture-tmux-compat-marker")
        },
        "capture-pane compatibility never observed marker",
    )
}

#[test]
fn capture_pane_matches_frozen_tmux_after_respawned_pane_exits_when_available(
) -> Result<(), Box<dyn Error>> {
    let harness = TmuxCompatHarness::new("capture-pane-dead-pane-tmux-compat")?;
    let tmux_binary = match FrozenTmuxBinary::discover() {
        FrozenTmuxBinary::Available(path) => path,
        FrozenTmuxBinary::Unavailable {
            checked_path,
            reason,
        } => {
            eprintln!(
                "runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
                checked_path.display()
            );
            return Ok(());
        }
    };
    let config = TmuxCompatRunConfig::default();

    let create = harness.run_pair_with(
        &tmux_binary,
        &["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&create);
    let remain_on_exit = harness.run_pair_with(
        &tmux_binary,
        &["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&remain_on_exit);

    let respawn = harness.run_pair_with(
        &tmux_binary,
        &[
            "respawn-pane",
            "-k",
            "-t",
            "alpha:0.0",
            "printf 'one\\ntwo\\n'; sleep 0.2",
        ],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&respawn);

    assert_capture_tmux_compat_when(
        &harness,
        &tmux_binary,
        &config,
        &["capture-pane", "-p", "-t", "alpha:0.0"],
        |run| run.tmux.stdout_string().contains("Pane is dead"),
        "capture-pane dead-pane compatibility never observed the dead pane marker",
    )
}

#[test]
fn capture_pane_matches_frozen_tmux_for_escape_range_and_quiet_flags_when_available(
) -> Result<(), Box<dyn Error>> {
    let harness = TmuxCompatHarness::new("capture-pane-flags-tmux-compat")?;
    let tmux_binary = match FrozenTmuxBinary::discover() {
        FrozenTmuxBinary::Available(path) => path,
        FrozenTmuxBinary::Unavailable {
            checked_path,
            reason,
        } => {
            eprintln!(
                "runtime skip: frozen tmux binary unavailable via {FROZEN_TMUX_ENV} or default '{}': {reason}",
                checked_path.display()
            );
            return Ok(());
        }
    };
    let config = TmuxCompatRunConfig::default();

    let create = harness.run_pair_with(
        &tmux_binary,
        &["new-session", "-d", "-s", "alpha", "-x", "80", "-y", "24"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&create);
    let remain_on_exit = harness.run_pair_with(
        &tmux_binary,
        &["set-window-option", "-t", "alpha:0", "remain-on-exit", "on"],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&remain_on_exit);

    let respawn = harness.run_pair_with(
        &tmux_binary,
        &[
            "respawn-pane",
            "-k",
            "-t",
            "alpha:0.0",
            "printf '\\033[31mcapture-flags-marker\\033[0m  \\nplain\\n'; sleep 10",
        ],
        config.clone(),
    )?;
    assert_exact_tmux_compat(&respawn);

    assert_capture_tmux_compat_when(
        &harness,
        &tmux_binary,
        &config,
        &["capture-pane", "-p", "-e", "-C", "-t", "alpha:0.0"],
        |run| {
            run.tmux
                .stdout_string()
                .contains("\\033[31mcapture-flags-marker\\033[39m")
        },
        "capture-pane -e -C compatibility never observed the escaped ANSI marker",
    )?;

    assert_capture_tmux_compat_when(
        &harness,
        &tmux_binary,
        &config,
        &[
            "capture-pane",
            "-p",
            "-S",
            "0",
            "-E",
            "0",
            "-t",
            "alpha:0.0",
        ],
        |run| run.tmux.stdout_string().contains("capture-flags-marker"),
        "capture-pane range compatibility never observed the marker line",
    )?;

    let alternate_quiet = harness.run_pair_with(
        &tmux_binary,
        &["capture-pane", "-p", "-a", "-q", "-t", "alpha:0.0"],
        config,
    )?;
    assert_exact_tmux_compat(&alternate_quiet);
    Ok(())
}

fn wait_for_capture(
    harness: &CliHarness,
    marker: &str,
) -> Result<std::process::Output, Box<dyn Error>> {
    let mut last = None;
    for _ in 0..100 {
        let output = harness.run(&["capture-pane", "-p", "-t", "alpha:0.0"])?;
        if output.status.code() == Some(0) && stdout(&output).contains(marker) {
            return Ok(output);
        }
        last = Some(output);
        std::thread::sleep(Duration::from_millis(20));
    }

    let last = last.expect("capture was attempted");
    Err(format!(
        "capture output never contained marker {marker}; status={:?} stdout={:?} stderr={:?}",
        last.status.code(),
        stdout(&last),
        stderr(&last)
    )
    .into())
}

fn assert_exact_tmux_compat(run: &TmuxCompatRun) {
    assert_eq!(run.tmux.status_code, run.rmux.status_code);
    assert_eq!(run.tmux.timed_out, run.rmux.timed_out);
    assert_eq!(run.tmux.stdout, run.rmux.stdout);
    assert_eq!(run.tmux.stderr, run.rmux.stderr);
}

fn assert_capture_tmux_compat_when<F>(
    harness: &TmuxCompatHarness,
    tmux_binary: &std::path::Path,
    config: &TmuxCompatRunConfig,
    argv: &[&str],
    ready: F,
    context: &str,
) -> Result<(), Box<dyn Error>>
where
    F: Fn(&TmuxCompatRun) -> bool,
{
    let mut last = None;
    for _ in 0..100 {
        let capture = harness.run_pair_with(tmux_binary, argv, config.clone())?;
        if capture.tmux.stdout == capture.rmux.stdout
            && capture.tmux.stderr == capture.rmux.stderr
            && capture.tmux.status_code == capture.rmux.status_code
            && ready(&capture)
        {
            assert_exact_tmux_compat(&capture);
            return Ok(());
        }
        last = Some(capture);
        std::thread::sleep(Duration::from_millis(20));
    }

    let last = last.expect("capture compatibility was attempted");
    Err(format!(
        "{context}; argv={argv:?}; tmux status={:?} stdout={:?} stderr={:?}; rmux status={:?} stdout={:?} stderr={:?}",
        last.tmux.status_code,
        last.tmux.stdout_string(),
        last.tmux.stderr_string(),
        last.rmux.status_code,
        last.rmux.stdout_string(),
        last.rmux.stderr_string()
    )
    .into())
}