rmux-sdk 0.1.0

Public, daemon-backed Rust SDK for the RMUX terminal multiplexer (facade, ensure-session, snapshots, events, detach helpers).
Documentation
use crate::handles::session::unexpected_response;
use crate::transport::TransportClient;
use crate::{
    PaneAttributes, PaneCell, PaneColor, PaneCursor, PaneGlyph, PaneRef, PaneSnapshot, Result,
};
use rmux_proto::{
    PaneSnapshotCell, PaneSnapshotCursor, PaneSnapshotRequest, PaneSnapshotResponse, Request,
    Response,
};

use super::info::current_pane_entry;
use super::target::{is_already_closed_error, parse_error};

pub(super) async fn pane_snapshot(
    client: &TransportClient,
    target: &PaneRef,
) -> Result<PaneSnapshot> {
    if current_pane_entry(client, target).await?.is_none() {
        return Ok(PaneSnapshot::default());
    }

    // The pane was listed at the start of this call, but the daemon can still
    // close it between the existence check and the snapshot endpoint round
    // trip. Treat the already-closed protocol errors emitted in that window as
    // a "vanished mid-snapshot" signal and degrade to a default snapshot,
    // while genuine transport or protocol errors still propagate.
    match request_pane_snapshot(client, target).await {
        Ok(response) => snapshot_from_response(response),
        Err(error) if is_already_closed_error(&error, target) => Ok(PaneSnapshot::default()),
        Err(error) => Err(error),
    }
}

async fn request_pane_snapshot(
    client: &TransportClient,
    target: &PaneRef,
) -> Result<PaneSnapshotResponse> {
    let response = client
        .request(Request::PaneSnapshot(PaneSnapshotRequest {
            target: target.into(),
        }))
        .await?;

    match response {
        Response::PaneSnapshot(response) => Ok(response),
        response => Err(unexpected_response("pane-snapshot", response)),
    }
}

pub(super) fn snapshot_from_response(response: PaneSnapshotResponse) -> Result<PaneSnapshot> {
    let cells = response.cells.into_iter().map(cell_from_wire).collect();
    let cursor = cursor_from_wire(response.cursor);
    let snapshot = PaneSnapshot {
        cols: response.cols,
        rows: response.rows,
        cells,
        cursor,
        revision: response.revision,
    };
    snapshot.validate_shape().map_err(|error| {
        parse_error(format!(
            "pane-snapshot response had malformed row-major cell shape: {error}"
        ))
    })?;
    Ok(snapshot)
}

pub(super) fn cell_from_wire(cell: PaneSnapshotCell) -> PaneCell {
    let glyph = if cell.padding {
        PaneGlyph {
            text: cell.text,
            width: cell.width,
            padding: true,
        }
    } else {
        PaneGlyph::new(cell.text, cell.width)
    };
    PaneCell {
        glyph,
        attributes: PaneAttributes::from_bits(cell.attributes),
        foreground: PaneColor::from_encoded(cell.fg),
        background: PaneColor::from_encoded(cell.bg),
        underline: PaneColor::from_encoded(cell.us),
    }
}

fn cursor_from_wire(cursor: PaneSnapshotCursor) -> PaneCursor {
    PaneCursor::new(cursor.row, cursor.col, cursor.visible, cursor.style)
}