rmux-server 0.1.1

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

async fn enter_copy_mode_with_motion_seed(handler: &RequestHandler, target: &PaneTarget) -> String {
    replace_transcript_contents(
        handler,
        target,
        TerminalSize { cols: 80, rows: 24 },
        b"alpha beta gamma\r\nsecond beta line\r\nthird alpha marker\r\nfourth delta marker\r\nfifth beta tail\x1b[2;6H",
    )
    .await;
    assert!(matches!(
        handler
            .handle(Request::CopyMode(CopyModeRequest {
                target: Some(target.clone()),
                page_down: false,
                exit_on_scroll: false,
                hide_position: false,
                mouse_drag_start: false,
                cancel_mode: false,
                scrollbar_scroll: false,
                source: None,
                page_up: false,
            }))
            .await,
        Response::CopyMode(_)
    ));
    copy_motion_status(handler, target.clone()).await
}

async fn copy_motion_status(handler: &RequestHandler, target: PaneTarget) -> String {
    display_target_format(
        handler,
        target,
        "#{pane_in_mode}:#{copy_cursor_x},#{copy_cursor_y}",
    )
    .await
}

async fn send_copy_motion_key(
    handler: &RequestHandler,
    requester_pid: u32,
    pending_input: &mut Vec<u8>,
    bytes: &[u8],
) {
    let forwarded_to_pane = handler
        .handle_attached_live_input_inner(requester_pid, pending_input, bytes)
        .await
        .expect("copy-mode motion input");
    assert!(
        !forwarded_to_pane,
        "copy-mode motion keys must be consumed instead of forwarded to pane IO"
    );
    assert!(
        pending_input.is_empty(),
        "copy-mode motion input should fully decode and leave no pending bytes"
    );
}

#[tokio::test]
async fn attached_copy_mode_arrow_motion_routes_to_copy_handler_not_pane() {
    let handler = RequestHandler::new();
    let requester_pid = std::process::id();
    let alpha = session_name("alpha");
    let _control_rx = create_quiet_attached_session(&handler, requester_pid, &alpha).await;
    let target = PaneTarget::new(alpha.clone(), 0);

    assert_eq!(
        enter_copy_mode_with_motion_seed(&handler, &target).await,
        "1:5,1\n"
    );

    let before_capture = capture_pane_print(&handler, target.clone()).await;
    let mut pending_input = Vec::new();

    send_copy_motion_key(&handler, requester_pid, &mut pending_input, b"\x1b[C").await;
    assert_eq!(
        copy_motion_status(&handler, target.clone()).await,
        "1:6,1\n"
    );

    send_copy_motion_key(&handler, requester_pid, &mut pending_input, b"\x1b[D").await;
    assert_eq!(
        copy_motion_status(&handler, target.clone()).await,
        "1:5,1\n"
    );

    send_copy_motion_key(&handler, requester_pid, &mut pending_input, b"\x1b[B").await;
    assert_eq!(
        copy_motion_status(&handler, target.clone()).await,
        "1:5,2\n"
    );

    send_copy_motion_key(&handler, requester_pid, &mut pending_input, b"\x1b[A").await;
    assert_eq!(
        copy_motion_status(&handler, target.clone()).await,
        "1:5,1\n"
    );

    assert_eq!(
        capture_pane_print(&handler, target.clone()).await,
        before_capture,
        "copy-mode motion keys must not mutate the pane screen"
    );
}

#[tokio::test]
async fn attached_copy_mode_arrow_motion_exits_without_q_leak_and_resumes_pane_input() {
    let handler = RequestHandler::new();
    let requester_pid = std::process::id();
    let alpha = session_name("alpha");
    let _control_rx = create_quiet_attached_session(&handler, requester_pid, &alpha).await;
    let target = PaneTarget::new(alpha.clone(), 0);
    let _ = enter_copy_mode_with_motion_seed(&handler, &target).await;

    let mut pending_input = Vec::new();
    send_copy_motion_key(&handler, requester_pid, &mut pending_input, b"\x1b[C").await;

    let forwarded_to_pane = handler
        .handle_attached_live_input_inner(requester_pid, &mut pending_input, b"q")
        .await
        .expect("q exits copy-mode after motion");
    assert!(
        !forwarded_to_pane,
        "q must be consumed by copy-mode instead of forwarded to pane IO"
    );
    assert_eq!(pane_mode_status(&handler, &alpha).await, "0:::\n");
    assert!(
        !capture_pane_print(&handler, target.clone())
            .await
            .contains("\nq"),
        "q must not appear in the pane capture after copy-mode dismiss"
    );

    let forwarded_to_pane = handler
        .handle_attached_live_input_inner(
            requester_pid,
            &mut pending_input,
            b"RMUX_AFTER_COPY_MOTION",
        )
        .await
        .expect("normal input resumes after copy-mode");
    assert!(
        forwarded_to_pane,
        "normal pane input should resume after copy-mode dismiss"
    );
}