rmux-server 0.10.0

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use super::*;

async fn create_window_listing_fixture(handler: &RequestHandler) {
    for (session, size, first_name, second_name) in [
        (
            "beta",
            TerminalSize { cols: 80, rows: 30 },
            "zebra",
            "apple",
        ),
        (
            "alpha",
            TerminalSize {
                cols: 100,
                rows: 20,
            },
            "mango",
            "berry",
        ),
    ] {
        let session = session_name(session);
        let response = handler
            .handle(Request::NewSession(NewSessionRequest {
                session_name: session.clone(),
                detached: true,
                size: Some(size),
                environment: None,
            }))
            .await;
        assert!(matches!(response, Response::NewSession(_)), "{response:?}");
        let response = handler
            .handle(Request::RenameWindow(rmux_proto::RenameWindowRequest {
                target: WindowTarget::with_window(session.clone(), 0),
                name: first_name.to_owned(),
            }))
            .await;
        assert!(
            matches!(response, Response::RenameWindow(_)),
            "{response:?}"
        );
        let response = handler
            .handle(Request::NewWindow(Box::new(NewWindowRequest {
                target: session,
                name: Some(second_name.to_owned()),
                detached: true,
                environment: None,
                command: None,
                start_directory: None,
                target_window_index: None,
                insert_at_target: false,
                process_command: None,
            })))
            .await;
        assert!(matches!(response, Response::NewWindow(_)), "{response:?}");
    }
    handler.wait_for_initial_panes_for_test().await;
}

async fn execute_list_windows_all(handler: &RequestHandler, command: &str) -> String {
    let parsed = CommandParser::new()
        .parse(command)
        .expect("list-windows -a parses");
    let output = handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect("list-windows -a executes");
    String::from_utf8(output.stdout().to_vec()).expect("list-windows output is utf-8")
}

#[tokio::test]
async fn parsed_queue_list_windows_all_formats_filters_and_ignores_target() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;

    let stdout = execute_list_windows_all(
        &handler,
        "list-windows -a -t beta -f '#{==:#{session_name},alpha}' -F '#{session_name}:#{window_index}:#{window_name}'",
    )
    .await;
    assert_eq!(stdout, "alpha:0:mango\nalpha:1:berry\n");
}

#[tokio::test]
async fn parsed_queue_list_windows_all_applies_global_sort_and_reverse() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;

    let by_name = execute_list_windows_all(
        &handler,
        "list-windows -a -O name -F '#{session_name}:#{window_index}:#{window_name}'",
    )
    .await;
    assert_eq!(
        by_name,
        "beta:1:apple\nalpha:1:berry\nalpha:0:mango\nbeta:0:zebra\n"
    );

    let reversed = execute_list_windows_all(
        &handler,
        "list-windows -arOname -F '#{session_name}:#{window_index}:#{window_name}'",
    )
    .await;
    assert_eq!(
        reversed,
        "beta:0:zebra\nalpha:0:mango\nalpha:1:berry\nbeta:1:apple\n"
    );

    let by_size = execute_list_windows_all(
        &handler,
        "list-windows -a -O size -F '#{session_name}:#{window_index}:#{window_width}x#{window_height}'",
    )
    .await;
    assert_eq!(
        by_size,
        "alpha:0:100x20\nalpha:1:100x20\nbeta:0:80x30\nbeta:1:80x30\n"
    );
}

#[tokio::test]
async fn parsed_queue_list_windows_all_uses_tmux_default_format() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;

    let stdout = execute_list_windows_all(&handler, "list-windows -a").await;
    assert!(stdout.contains("alpha:0: mango"), "{stdout:?}");
    assert!(stdout.contains("beta:1: apple"), "{stdout:?}");
    assert!(
        stdout.find("alpha:0:") < stdout.find("beta:0:"),
        "default all-session order should be session then window: {stdout:?}"
    );
}

#[cfg(windows)]
#[tokio::test]
async fn queued_list_windows_all_waits_for_deferred_windows_pane_pids() {
    let handler = RequestHandler::new();
    let parsed = CommandParser::new()
        .parse(
            "new-session -d -s deferred-pid-list ; \
             list-windows -a -f '#{pane_pid}' -F '#{pane_pid}'",
        )
        .expect("deferred pane-pid queue parses");

    let output = handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect("deferred pane-pid queue executes");
    let pane_pid = String::from_utf8(output.stdout().to_vec()).expect("pane pid is utf-8");
    let pane_pid = pane_pid.trim().parse::<u32>().expect("pane pid is numeric");
    assert_ne!(pane_pid, 0);
}

#[tokio::test]
async fn source_file_and_control_queue_share_list_windows_all_execution() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;
    let root = temp_root("list-windows-all");
    fs::create_dir_all(&root).expect("create source root");
    fs::write(
        root.join("all.conf"),
        "list-windows -a -F '#{session_name}:#{window_index}'\n",
    )
    .expect("write source file");

    let response = handler
        .handle(source_file_request(
            vec!["all.conf".to_owned()],
            Some(root.clone()),
        ))
        .await;
    let Response::SourceFile(response) = response else {
        panic!("expected source-file response: {response:?}");
    };
    let source_stdout = String::from_utf8(
        response
            .command_output()
            .expect("source-file carries list output")
            .stdout()
            .to_vec(),
    )
    .expect("source output is utf-8");
    assert_eq!(source_stdout, "alpha:0\nalpha:1\nbeta:0\nbeta:1\n");

    let requester_pid = 49_102;
    let (event_tx, _event_rx) = tokio::sync::mpsc::channel(CONTROL_SERVER_EVENT_CAPACITY);
    handler
        .register_control_with_closing(
            requester_pid,
            ControlModeUpgrade {
                initial_command_count: 0,
                mode: rmux_proto::ControlMode::Plain,
                terminal_context: OuterTerminalContext::default(),
            },
            event_tx,
            Arc::new(AtomicBool::new(false)),
        )
        .await;
    handler
        .set_control_session(requester_pid, Some(session_name("alpha")))
        .await
        .expect("control session set succeeds");
    let commands = handler
        .parse_control_commands(
            "list-windows -a -f '#{==:#{session_name},beta}' -F '#{session_name}:#{window_index}'",
        )
        .await
        .expect("control list-windows parses");
    let result = handler
        .execute_control_commands(requester_pid, commands)
        .await;
    assert_eq!(result.error, None, "{:?}", result.error);
    assert_eq!(result.stdout, b"beta:0\nbeta:1\n");

    fs::remove_dir_all(root).expect("remove source root");
}

#[tokio::test]
async fn queued_list_windows_all_runs_its_tmux_after_hook() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;
    let parsed = CommandParser::new()
        .parse("set-hook -g after-list-windows { set-buffer -b list-windows-hook fired }")
        .expect("after-list-windows hook parses");
    handler
        .execute_parsed_commands_for_test(std::process::id(), parsed)
        .await
        .expect("after-list-windows hook installs");

    let _ = execute_list_windows_all(&handler, "list-windows -a -F '#{session_name}'").await;
    wait_for_named_buffer(&handler, "list-windows-hook", b"fired").await;
}

#[tokio::test]
async fn queued_list_windows_all_validates_target_and_scopes_success_and_error_hooks() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;
    let hooks = CommandParser::new()
        .parse(
            "set-hook -t alpha after-list-windows 'set-buffer -b alpha-list-success yes' ; \
             set-hook -t beta after-list-windows 'set-buffer -b beta-list-success yes' ; \
             set-hook -t alpha command-error 'set-buffer -b alpha-list-error yes' ; \
             set-hook -t beta command-error 'set-buffer -b beta-list-error yes'",
        )
        .expect("scoped list hooks parse");
    handler
        .execute_parsed_commands_for_test(std::process::id(), hooks)
        .await
        .expect("scoped list hooks install");

    let stdout = execute_list_windows_all(
        &handler,
        "list-windows -a -t beta -F '#{session_name}:#{window_index}'",
    )
    .await;
    assert!(stdout.starts_with("alpha:0\n"), "{stdout:?}");
    wait_for_named_buffer(&handler, "beta-list-success", b"yes").await;
    assert!(handler
        .state
        .lock()
        .await
        .buffers
        .get("alpha-list-success")
        .is_none());

    let invalid_sort = CommandParser::new()
        .parse("list-windows -a -t beta -O definitely-invalid")
        .expect("invalid sort command parses structurally");
    let error = handler
        .execute_parsed_commands_for_test(std::process::id(), invalid_sort)
        .await
        .expect_err("invalid sort should fail");
    assert!(error.to_string().contains(rmux_core::INVALID_SORT_ORDER));
    wait_for_named_buffer(&handler, "beta-list-error", b"yes").await;
    assert!(handler
        .state
        .lock()
        .await
        .buffers
        .get("alpha-list-error")
        .is_none());

    let missing_target = CommandParser::new()
        .parse("list-windows -a -t definitely-missing")
        .expect("missing target command parses structurally");
    let error = handler
        .execute_parsed_commands_for_test(std::process::id(), missing_target)
        .await
        .expect_err("missing target should fail before listing");
    assert!(error.to_string().contains("can't find session"), "{error}");
}

#[tokio::test]
async fn hook_binding_and_alias_entries_share_list_windows_all_execution() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;

    let hook = CommandParser::new()
        .parse(
            r#"set-hook -g after-new-window "run-shell -C 'list-windows -a ; set-buffer -b list-windows-hook-entry ok'""#,
        )
        .expect("hook body parses");
    handler
        .execute_parsed_commands_for_test(std::process::id(), hook)
        .await
        .expect("hook installs");
    let new_window = CommandParser::new()
        .parse("new-window -d -t alpha")
        .expect("new-window parses");
    handler
        .execute_parsed_commands_for_test(std::process::id(), new_window)
        .await
        .expect("new-window and hook execute");
    wait_for_named_buffer(&handler, "list-windows-hook-entry", b"ok").await;

    let requester_pid = u32::MAX - 91_102;
    let (attach_tx, _attach_rx) = tokio::sync::mpsc::unbounded_channel();
    handler
        .register_attach(requester_pid, session_name("alpha"), attach_tx)
        .await;
    let binding = CommandParser::new()
        .parse(
            r#"bind-key X "run-shell -C 'list-windows -a ; set-buffer -b list-windows-binding-entry ok'""#,
        )
        .expect("binding body parses");
    handler
        .execute_parsed_commands_for_test(requester_pid, binding)
        .await
        .expect("binding installs");
    handler
        .handle_attached_live_input_for_test(requester_pid, b"\x02X")
        .await
        .expect("binding executes");
    wait_for_named_buffer(&handler, "list-windows-binding-entry", b"ok").await;

    assert!(matches!(
        handler
            .handle(Request::SetOption(SetOptionRequest {
                scope: ScopeSelector::Global,
                option: OptionName::CommandAlias,
                value: "lwa=list-windows -a -F '#{session_name}:#{window_index}'".to_owned(),
                mode: SetOptionMode::Replace,
            }))
            .await,
        Response::SetOption(_)
    ));
    let alias = handler
        .parse_command_string_one_group("lwa")
        .await
        .expect("runtime alias parses");
    let output = handler
        .execute_parsed_commands_for_test(requester_pid, alias)
        .await
        .expect("runtime alias executes");
    assert_eq!(
        output.stdout(),
        b"alpha:0\nalpha:1\nalpha:2\nbeta:0\nbeta:1\n"
    );
}

#[tokio::test]
async fn startup_config_entry_executes_list_windows_all_and_continues() {
    let handler = RequestHandler::new();
    create_window_listing_fixture(&handler).await;
    let root = temp_root("list-windows-all-startup");
    fs::create_dir_all(&root).expect("create startup root");
    fs::write(
        root.join("startup.conf"),
        "list-windows -a -F '#{session_name}:#{window_index}'\nset-option -g @list-windows-startup ok\n",
    )
    .expect("write startup config");
    let config = crate::DaemonConfig::new(root.join("rmux.sock")).with_config_files(
        vec![std::path::PathBuf::from("startup.conf")],
        false,
        Some(root.clone()),
    );

    handler
        .load_startup_config(config.config_load().clone())
        .await;
    assert!(
        handler.startup_config_errors.lock().await.is_empty(),
        "startup list-windows -a should not produce a config error"
    );
    let show = CommandParser::new()
        .parse("show-options -gqv @list-windows-startup")
        .expect("show startup marker parses");
    let output = handler
        .execute_parsed_commands_for_test(std::process::id(), show)
        .await
        .expect("startup marker is readable");
    assert_eq!(output.stdout(), b"ok\n");

    fs::remove_dir_all(root).expect("remove startup root");
}