rho-coding-agent 1.48.0

A lightweight agent harness inspired by Pi
//! Who owns the full terminal: setup, in-place attach, or the session.
//!
//! This is stored state, not a query over sibling flags. Setup and attach both
//! replace session chrome. Only attach swallows input; setup still types into
//! its composer pickers.

use std::time::Instant;

use crossterm::event::Event;
use ratatui::Frame;

use super::{attachment::AttachmentApp, setup_screen::SetupStep, App};

/// Foreground occupant of the interactive TUI.
pub(super) enum ExclusiveOccupant {
    Session,
    Setup(SetupStep),
    Attach {
        view: Box<AttachmentApp>,
        parent_turn_armed: bool,
    },
}

impl ExclusiveOccupant {
    pub(super) fn setup_step(&self) -> Option<SetupStep> {
        match self {
            Self::Setup(step) => Some(*step),
            Self::Session | Self::Attach { .. } => None,
        }
    }

    pub(super) fn wants_journal_ticks(&self) -> bool {
        matches!(self, Self::Attach { .. })
    }

    pub(super) fn parent_turn_armed(&self) -> Option<bool> {
        match self {
            Self::Attach {
                parent_turn_armed, ..
            } => Some(*parent_turn_armed),
            Self::Session | Self::Setup(_) => None,
        }
    }

    pub(super) fn attach_view(&self) -> Option<&AttachmentApp> {
        match self {
            Self::Attach { view, .. } => Some(view),
            Self::Session | Self::Setup(_) => None,
        }
    }

    pub(super) fn attach_view_mut(&mut self) -> Option<&mut AttachmentApp> {
        match self {
            Self::Attach { view, .. } => Some(view),
            Self::Session | Self::Setup(_) => None,
        }
    }
}

impl App {
    pub(super) fn draw_exclusive_screen(&mut self, frame: &mut Frame<'_>) -> bool {
        match self.exclusive {
            ExclusiveOccupant::Setup(step) => {
                let area = frame.area();
                self.draw_setup_screen(frame, area, step);
                true
            }
            ExclusiveOccupant::Attach { .. } => self.draw_attach_screen(frame),
            ExclusiveOccupant::Session => false,
        }
    }

    /// Consume a terminal event when the occupant owns the keyboard.
    ///
    /// Setup is exclusive paint only, so its events are returned for the
    /// composer. Attach consumes every event and reports whether it was a resize.
    pub(super) fn take_exclusive_event(&mut self, event: Event) -> Result<bool, Event> {
        match self.exclusive {
            ExclusiveOccupant::Attach { .. } => Ok(self.route_attach_event(event)),
            ExclusiveOccupant::Setup(_) | ExclusiveOccupant::Session => Err(event),
        }
    }

    pub(super) fn exclusive_should_redraw(&self, now: Instant) -> bool {
        self.exclusive
            .attach_view()
            .is_some_and(|view| view.should_redraw(now))
    }

    pub(super) fn refresh_exclusive_screen(&mut self) -> anyhow::Result<bool> {
        let Some(view) = self.exclusive.attach_view_mut() else {
            return Ok(false);
        };
        let changed = view.refresh()?;
        Ok(changed || view.should_redraw(Instant::now()))
    }
}