triage-core 0.1.1

Shared session trait and types for Triage, the attention-routing terminal supervisor.
use crate::generated::triage::generated as fb;
use crate::session::{
    AttachSessionResponse, CompletedSession, InputLeaseHolder, InputLeaseState, LeaseChange,
    SessionContext, SessionSize, SessionSnapshot, StyledRow, StyledRowsResponse, StyledSpan,
    TerminalColor, TerminalCursor, TerminalStyle,
};
use flatbuffers::FlatBufferBuilder;

impl From<&SessionSize> for fb::SessionSize {
    fn from(s: &SessionSize) -> Self {
        fb::SessionSize::new(
            s.rows as u32,
            s.cols as u32,
            s.pixel_width as u32,
            s.pixel_height as u32,
            s.dpi as u32,
        )
    }
}

impl From<&TerminalCursor> for fb::TerminalCursor {
    fn from(c: &TerminalCursor) -> Self {
        fb::TerminalCursor::new(c.row as u32, c.col as u32, c.visible)
    }
}

impl From<TerminalColor> for fb::TerminalColor {
    fn from(c: TerminalColor) -> Self {
        fb::TerminalColor::new(c.red, c.green, c.blue)
    }
}

impl From<&TerminalStyle> for fb::TerminalStyle {
    fn from(s: &TerminalStyle) -> Self {
        let has_fg = s.foreground.is_some();
        let fg = s
            .foreground
            .map(fb::TerminalColor::from)
            .unwrap_or_else(|| fb::TerminalColor::new(0, 0, 0));
        let has_bg = s.background.is_some();
        let bg = s
            .background
            .map(fb::TerminalColor::from)
            .unwrap_or_else(|| fb::TerminalColor::new(0, 0, 0));
        fb::TerminalStyle::new(
            &fg,
            has_fg,
            &bg,
            has_bg,
            s.bold,
            s.dim,
            s.italic,
            s.underline,
            s.reverse,
        )
    }
}

pub fn build_styled_span<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    span: &StyledSpan,
) -> flatbuffers::WIPOffset<fb::StyledSpan<'a>> {
    let text = builder.create_string(&span.text);
    let style = fb::TerminalStyle::from(&span.style);
    fb::StyledSpan::create(
        builder,
        &fb::StyledSpanArgs {
            text: Some(text),
            style: Some(&style),
        },
    )
}

pub fn build_styled_row<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    row: &StyledRow,
) -> flatbuffers::WIPOffset<fb::StyledRow<'a>> {
    let mut spans = Vec::new();
    for span in &row.spans {
        spans.push(build_styled_span(builder, span));
    }
    let spans_vec = builder.create_vector(&spans);
    fb::StyledRow::create(
        builder,
        &fb::StyledRowArgs {
            spans: Some(spans_vec),
        },
    )
}

pub fn build_session_context<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    ctx: &SessionContext,
) -> flatbuffers::WIPOffset<fb::SessionContext<'a>> {
    let repo = ctx
        .repository_root
        .as_ref()
        .map(|p| builder.create_string(&p.to_string_lossy()));
    let wt = ctx
        .worktree_root
        .as_ref()
        .map(|p| builder.create_string(&p.to_string_lossy()));
    let branch = ctx.branch.as_ref().map(|b| builder.create_string(b));
    fb::SessionContext::create(
        builder,
        &fb::SessionContextArgs {
            repository_root: repo,
            worktree_root: wt,
            branch,
        },
    )
}

pub fn build_session_snapshot<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    snap: &SessionSnapshot,
) -> flatbuffers::WIPOffset<fb::SessionSnapshot<'a>> {
    let size = fb::SessionSize::from(&snap.size);
    let mut visible = Vec::new();
    for r in &snap.visible_rows {
        visible.push(builder.create_string(r));
    }
    let visible_vec = builder.create_vector(&visible);

    let mut styled = Vec::new();
    for r in &snap.styled_rows {
        styled.push(build_styled_row(builder, r));
    }
    let styled_vec = builder.create_vector(&styled);

    let cursor = fb::TerminalCursor::from(&snap.cursor);
    let cwd = snap
        .current_working_directory
        .as_ref()
        .map(|p| builder.create_string(&p.to_string_lossy()));
    let context = snap
        .context
        .as_ref()
        .map(|c| build_session_context(builder, c));

    fb::SessionSnapshot::create(
        builder,
        &fb::SessionSnapshotArgs {
            output_seq: snap.output_seq,
            bytes_logged: snap.bytes_logged,
            size: Some(&size),
            visible_rows: Some(visible_vec),
            styled_rows_start: snap.styled_rows_start as u32,
            styled_rows: Some(styled_vec),
            cursor: Some(&cursor),
            current_working_directory: cwd,
            context,
            bracketed_paste_enabled: snap.bracketed_paste_enabled,
            exited: snap.exited,
        },
    )
}

pub fn build_completed_session<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    comp: &CompletedSession,
) -> flatbuffers::WIPOffset<fb::CompletedSession<'a>> {
    let mut rows = Vec::new();
    for r in &comp.visible_rows {
        rows.push(builder.create_string(r));
    }
    let rows_vec = builder.create_vector(&rows);
    fb::CompletedSession::create(
        builder,
        &fb::CompletedSessionArgs {
            output_seq: comp.output_seq,
            bytes_logged: comp.bytes_logged,
            visible_rows: Some(rows_vec),
        },
    )
}

pub fn build_input_lease_holder<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    holder: &InputLeaseHolder,
) -> flatbuffers::WIPOffset<fb::InputLeaseHolder<'a>> {
    let cid = builder.create_string(holder.client_id.as_str());
    let kind = match holder.kind {
        crate::session::InputControllerKind::Interactive => fb::InputControllerKind::Interactive,
        crate::session::InputControllerKind::Agent => fb::InputControllerKind::Agent,
    };
    fb::InputLeaseHolder::create(
        builder,
        &fb::InputLeaseHolderArgs {
            client_id: Some(cid),
            kind,
        },
    )
}

pub fn build_input_lease_state<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    state: &InputLeaseState,
) -> flatbuffers::WIPOffset<fb::InputLeaseState<'a>> {
    let holder = state
        .holder
        .as_ref()
        .map(|h| build_input_lease_holder(builder, h));
    fb::InputLeaseState::create(
        builder,
        &fb::InputLeaseStateArgs {
            holder,
            generation: state.generation,
        },
    )
}

pub fn build_lease_change<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    change: &LeaseChange,
) -> flatbuffers::WIPOffset<fb::LeaseChange<'a>> {
    let prev = change
        .previous
        .as_ref()
        .map(|h| build_input_lease_holder(builder, h));
    let cur = change
        .current
        .as_ref()
        .map(|h| build_input_lease_holder(builder, h));
    let action = match change.action {
        crate::session::LeaseChangeAction::Acquired => fb::LeaseChangeAction::Acquired,
        crate::session::LeaseChangeAction::Released => fb::LeaseChangeAction::Released,
        crate::session::LeaseChangeAction::TakenOver => fb::LeaseChangeAction::TakenOver,
    };
    fb::LeaseChange::create(
        builder,
        &fb::LeaseChangeArgs {
            generation: change.generation,
            previous: prev,
            current: cur,
            action,
        },
    )
}

pub fn build_attach_session_response<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    res: &AttachSessionResponse,
) -> flatbuffers::WIPOffset<fb::AttachSessionResponse<'a>> {
    let snap = build_session_snapshot(builder, &res.snapshot);
    let lease = build_input_lease_state(builder, &res.lease);
    fb::AttachSessionResponse::create(
        builder,
        &fb::AttachSessionResponseArgs {
            snapshot: Some(snap),
            lease: Some(lease),
        },
    )
}

pub fn build_styled_rows_response<'a>(
    builder: &mut FlatBufferBuilder<'a>,
    res: &StyledRowsResponse,
) -> flatbuffers::WIPOffset<fb::StyledRowsResponse<'a>> {
    let mut rows = Vec::new();
    for r in &res.rows {
        rows.push(build_styled_row(builder, r));
    }
    let rows_vec = builder.create_vector(&rows);
    fb::StyledRowsResponse::create(
        builder,
        &fb::StyledRowsResponseArgs {
            output_seq: res.output_seq,
            start: res.start as u32,
            rows: Some(rows_vec),
        },
    )
}