rmux-server 0.6.1

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

#[cfg(unix)]
#[tokio::test]
async fn live_attach_direct_fast_path_requires_current_unsynchronized_pane() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");
    let requester_pid = std::process::id();

    create_send_keys_test_session(&handler, &alpha).await;
    let (control_tx, _control_rx) = mpsc::unbounded_channel();
    let _attach_id = handler
        .register_attach(requester_pid, alpha.clone(), control_tx)
        .await;

    let pane_zero = PaneTarget::with_window(alpha.clone(), 0, 0);
    let pty = rmux_pty::PtyPair::open().expect("open pty pair");

    let direct = handler
        .try_forward_plain_attached_bytes_to_current_pane_fast(
            requester_pid,
            &[],
            b"x",
            &pane_zero,
            pty.master(),
        )
        .await
        .expect("direct fast path should run");
    assert_eq!(direct, Some(true));

    let direct_batch = handler
        .try_forward_plain_attached_bytes_to_current_pane_fast(
            requester_pid,
            &[],
            b"abc",
            &pane_zero,
            pty.master(),
        )
        .await
        .expect("direct fast path should accept small printable batches");
    assert_eq!(direct_batch, Some(true));

    let stale_target = PaneTarget::with_window(alpha.clone(), 0, 1);
    let stale = handler
        .try_forward_plain_attached_bytes_to_current_pane_fast(
            requester_pid,
            &[],
            b"x",
            &stale_target,
            pty.master(),
        )
        .await
        .expect("stale target check should run");
    assert_eq!(stale, None);

    let set_sync = handler
        .handle(Request::SetOption(SetOptionRequest {
            scope: ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 0)),
            option: OptionName::SynchronizePanes,
            value: "on".to_owned(),
            mode: SetOptionMode::Replace,
        }))
        .await;
    assert!(matches!(set_sync, Response::SetOption(_)));

    let synchronized = handler
        .try_forward_plain_attached_bytes_to_current_pane_fast(
            requester_pid,
            &[],
            b"x",
            &pane_zero,
            pty.master(),
        )
        .await
        .expect("sync panes check should run");
    assert_eq!(synchronized, None);
}

#[tokio::test]
async fn live_attach_synchronize_panes_writes_to_each_live_pane() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");
    let requester_pid = std::process::id();

    create_send_keys_test_session(&handler, &alpha).await;
    let split = handler
        .handle(Request::SplitWindow(SplitWindowRequest {
            target: SplitWindowTarget::Pane(PaneTarget::new(alpha.clone(), 0)),
            direction: SplitDirection::Horizontal,
            before: false,
            environment: None,
        }))
        .await;
    assert!(matches!(split, Response::SplitWindow(_)));

    let select_first = handler
        .handle(Request::SelectPane(SelectPaneRequest {
            target: PaneTarget::with_window(alpha.clone(), 0, 0),
            title: None,
            style: None,
            input_disabled: None,
            preserve_zoom: false,
        }))
        .await;
    assert!(matches!(select_first, Response::SelectPane(_)));

    let set_sync = handler
        .handle(Request::SetOption(SetOptionRequest {
            scope: ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 0)),
            option: OptionName::SynchronizePanes,
            value: "on".to_owned(),
            mode: SetOptionMode::Replace,
        }))
        .await;
    assert!(matches!(set_sync, Response::SetOption(_)));

    let pane_zero = PaneTarget::with_window(alpha.clone(), 0, 0);
    let pane_one = PaneTarget::with_window(alpha.clone(), 0, 1);
    {
        let state = handler.state.lock().await;
        state.start_pane_input_capture_for_test(&pane_zero);
        state.start_pane_input_capture_for_test(&pane_one);
    }

    let (control_tx, _control_rx) = mpsc::unbounded_channel();
    let _attach_id = handler
        .register_attach(requester_pid, alpha.clone(), control_tx)
        .await;

    handler
        .handle_attached_live_input_for_test(requester_pid, b"sync")
        .await
        .expect("live attach input");

    let state = handler.state.lock().await;
    assert_eq!(
        state.pane_input_capture_for_test(&pane_zero),
        Some(b"sync".to_vec())
    );
    assert_eq!(
        state.pane_input_capture_for_test(&pane_one),
        Some(b"sync".to_vec())
    );
}

#[tokio::test]
async fn send_keys_synchronize_panes_writes_to_each_live_pane() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");

    create_synchronized_two_pane_session(&handler, &alpha).await;

    let pane_zero = PaneTarget::with_window(alpha.clone(), 0, 0);
    let pane_one = PaneTarget::with_window(alpha.clone(), 0, 1);
    {
        let state = handler.state.lock().await;
        state.start_pane_input_capture_for_test(&pane_zero);
        state.start_pane_input_capture_for_test(&pane_one);
    }

    let response = handler
        .handle(Request::SendKeys(SendKeysRequest {
            target: pane_zero.clone(),
            keys: vec!["sync".to_owned()],
        }))
        .await;
    assert!(matches!(
        response,
        Response::SendKeys(SendKeysResponse { key_count: 1 })
    ));

    let state = handler.state.lock().await;
    assert_eq!(
        state.pane_input_capture_for_test(&pane_zero),
        Some(b"sync".to_vec())
    );
    assert_eq!(
        state.pane_input_capture_for_test(&pane_one),
        Some(b"sync".to_vec())
    );
}

#[tokio::test]
async fn send_prefix_synchronize_panes_writes_to_each_live_pane() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");

    create_synchronized_two_pane_session(&handler, &alpha).await;

    let pane_zero = PaneTarget::with_window(alpha.clone(), 0, 0);
    let pane_one = PaneTarget::with_window(alpha.clone(), 0, 1);
    {
        let state = handler.state.lock().await;
        state.start_pane_input_capture_for_test(&pane_zero);
        state.start_pane_input_capture_for_test(&pane_one);
    }

    let response = handler
        .handle(Request::SendPrefix(SendPrefixRequest {
            target: Some(pane_zero.clone()),
            secondary: false,
        }))
        .await;
    assert!(matches!(
        response,
        Response::SendPrefix(SendPrefixResponse { key_count: 1, .. })
    ));

    let state = handler.state.lock().await;
    assert_eq!(
        state.pane_input_capture_for_test(&pane_zero),
        Some(b"\x02".to_vec())
    );
    assert_eq!(
        state.pane_input_capture_for_test(&pane_one),
        Some(b"\x02".to_vec())
    );
}

async fn create_synchronized_two_pane_session(
    handler: &RequestHandler,
    alpha: &rmux_proto::SessionName,
) {
    create_send_keys_test_session(handler, alpha).await;
    let split = handler
        .handle(Request::SplitWindow(SplitWindowRequest {
            target: SplitWindowTarget::Pane(PaneTarget::new(alpha.clone(), 0)),
            direction: SplitDirection::Horizontal,
            before: false,
            environment: None,
        }))
        .await;
    assert!(matches!(split, Response::SplitWindow(_)));

    let select_first = handler
        .handle(Request::SelectPane(SelectPaneRequest {
            target: PaneTarget::with_window(alpha.clone(), 0, 0),
            title: None,
            style: None,
            input_disabled: None,
            preserve_zoom: false,
        }))
        .await;
    assert!(matches!(select_first, Response::SelectPane(_)));

    let set_sync = handler
        .handle(Request::SetOption(SetOptionRequest {
            scope: ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 0)),
            option: OptionName::SynchronizePanes,
            value: "on".to_owned(),
            mode: SetOptionMode::Replace,
        }))
        .await;
    assert!(matches!(set_sync, Response::SetOption(_)));
}