rmux-server 0.1.1

Tokio daemon and request dispatcher for the RMUX terminal multiplexer.
Documentation
use tokio::sync::mpsc;

use super::RequestHandler;
use crate::pane_io::AttachControl;
use rmux_proto::{
    DisplayPanesRequest, DisplayPanesResponse, NewSessionRequest, PaneTarget, Request,
    ResizePaneAdjustment, ResizePaneRequest, Response, SessionName, SplitDirection,
    SplitWindowRequest, SplitWindowTarget, TerminalSize, WindowTarget,
};

fn session_name(value: &str) -> SessionName {
    SessionName::new(value).expect("valid session name")
}

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

    assert!(matches!(
        handler
            .handle(Request::NewSession(NewSessionRequest {
                session_name: alpha.clone(),
                detached: true,
                size: Some(TerminalSize { cols: 80, rows: 24 }),
                environment: None,
            }))
            .await,
        Response::NewSession(_)
    ));
    assert!(matches!(
        handler
            .handle(Request::SplitWindow(SplitWindowRequest {
                target: SplitWindowTarget::Session(alpha.clone()),
                direction: SplitDirection::Horizontal,
                before: false,
                environment: None,
            }))
            .await,
        Response::SplitWindow(_)
    ));

    let response = handler
        .handle(Request::ResizePane(ResizePaneRequest {
            target: PaneTarget::new(alpha.clone(), 1),
            adjustment: ResizePaneAdjustment::Zoom,
        }))
        .await;

    assert_eq!(
        response,
        Response::ResizePane(rmux_proto::ResizePaneResponse {
            target: PaneTarget::new(alpha.clone(), 1),
            adjustment: ResizePaneAdjustment::Zoom,
        })
    );

    let state = handler.state.lock().await;
    assert!(state
        .sessions
        .session(&alpha)
        .expect("session exists")
        .window()
        .is_zoomed());
}

#[tokio::test]
async fn display_panes_sends_overlay_to_attached_session_without_waiting_for_clear() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");
    let (control_tx, mut control_rx) = mpsc::unbounded_channel();

    assert!(matches!(
        handler
            .handle(Request::NewSession(NewSessionRequest {
                session_name: alpha.clone(),
                detached: true,
                size: Some(TerminalSize { cols: 8, rows: 4 }),
                environment: None,
            }))
            .await,
        Response::NewSession(_)
    ));
    assert!(matches!(
        handler
            .handle(Request::SplitWindow(SplitWindowRequest {
                target: SplitWindowTarget::Session(alpha.clone()),
                direction: SplitDirection::Horizontal,
                before: false,
                environment: None,
            }))
            .await,
        Response::SplitWindow(_)
    ));
    handler.register_attach(42, alpha.clone(), control_tx).await;

    let response = handler
        .handle(Request::DisplayPanes(DisplayPanesRequest {
            target: alpha.clone(),
            duration_ms: None,
            non_blocking: false,
            no_command: false,
            template: None,
        }))
        .await;

    assert_eq!(
        response,
        Response::DisplayPanes(DisplayPanesResponse {
            target: WindowTarget::new(alpha),
            pane_count: 2,
        })
    );
    let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(1);
    let mut saw_display_panes_overlay = false;
    while tokio::time::Instant::now() < deadline {
        let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
        let next = tokio::time::timeout(remaining, control_rx.recv())
            .await
            .expect("display-panes control should arrive");
        let Some(next) = next else {
            break;
        };
        if let AttachControl::Overlay(overlay) = next {
            let frame = String::from_utf8(overlay.frame).expect("overlay is utf-8");
            if frame.contains("\u{1b}[41m") || frame.contains("\u{1b}[44m") {
                saw_display_panes_overlay = true;
                break;
            }
        }
    }
    assert!(
        saw_display_panes_overlay,
        "display-panes should emit an overlay frame with pane colours"
    );
}

#[tokio::test]
async fn display_panes_counts_only_labels_that_were_rendered() {
    let handler = RequestHandler::new();
    let alpha = session_name("alpha");
    let (control_tx, mut control_rx) = mpsc::unbounded_channel();

    {
        let mut state = handler.state.lock().await;
        state
            .sessions
            .create_session(alpha.clone(), TerminalSize { cols: 3, rows: 4 })
            .expect("session create succeeds");
        state
            .sessions
            .session_mut(&alpha)
            .expect("session exists")
            .split_active_pane()
            .expect("split succeeds");
        state
            .sessions
            .session_mut(&alpha)
            .expect("session exists")
            .resize_terminal(TerminalSize { cols: 3, rows: 1 });
    }
    handler.register_attach(43, alpha.clone(), control_tx).await;

    let response = handler
        .handle(Request::DisplayPanes(DisplayPanesRequest {
            target: alpha.clone(),
            duration_ms: None,
            non_blocking: false,
            no_command: false,
            template: None,
        }))
        .await;

    assert_eq!(
        response,
        Response::DisplayPanes(DisplayPanesResponse {
            target: WindowTarget::new(alpha),
            pane_count: 0,
        })
    );
    let overlay = control_rx.recv().await.expect("overlay control");
    let AttachControl::Overlay(overlay) = overlay else {
        panic!("expected display-panes overlay control");
    };
    assert!(overlay.frame.is_empty());
}