agentmux 0.8.0

Multi-agent coordination runtime with inter-agent messaging across CLI, MCP, tmux, and ACP.
Documentation
use ratatui::{Frame, layout::Rect};

use super::super::state::{AppState, FocusField, ScreenMode};
use super::frame::INTERACTION_RAWW_PANE_HEIGHT;
use super::geometry::{
    compose_message_layout, compose_message_visible_start, compose_titled_block, raww_titled_block,
    split_workbench_rows,
};

pub(super) fn render_active_cursor(frame: &mut Frame, area: Rect, state: &AppState) {
    if state.help_overlay_open || state.picker_open || state.events_overlay_open {
        return;
    }
    match state.mode {
        ScreenMode::Communication => render_compose_cursor(frame, area, state),
        ScreenMode::Interaction => render_raww_cursor(frame, area, state),
    }
}

fn render_compose_cursor(frame: &mut Frame, area: Rect, state: &AppState) {
    let rows = split_workbench_rows(area, state);
    let compose_inner = compose_titled_block("  Compose  ").inner(rows[1]);
    let Some((x, y)) = compose_cursor_position(compose_inner, state) else {
        return;
    };
    frame.set_cursor_position((x, y));
}

fn render_raww_cursor(frame: &mut Frame, area: Rect, state: &AppState) {
    if !state.interaction_raww_region_visible() {
        return;
    }
    let raww_area = interaction_raww_pane_area(area);
    let inner = raww_titled_block("  Write  ").inner(raww_area);
    if inner.width == 0 || inner.height == 0 {
        return;
    }
    let (cursor_line, cursor_column) = state.raww_cursor_line_and_column();
    let visible_x = (cursor_column as u16).min(inner.width.saturating_sub(1));
    let visible_y = (cursor_line as u16).min(inner.height.saturating_sub(1));
    frame.set_cursor_position((
        inner.x.saturating_add(visible_x),
        inner.y.saturating_add(visible_y),
    ));
}

fn compose_cursor_position(inner_area: Rect, state: &AppState) -> Option<(u16, u16)> {
    if inner_area.width == 0 || inner_area.height < 2 {
        return None;
    }
    let inner_left = inner_area.x;
    let inner_top = inner_area.y;
    let inner_right = inner_area
        .x
        .saturating_add(inner_area.width)
        .saturating_sub(1);
    let inner_bottom = inner_area
        .y
        .saturating_add(inner_area.height)
        .saturating_sub(1);
    let inner_width = inner_area.width;

    let (raw_x, raw_y) = match state.focus {
        FocusField::To => {
            let prefix_width = "To: ".chars().count() as u16;
            let field_width = inner_width.saturating_sub(prefix_width);
            let cursor_column = visible_cursor_column_count(state.to_cursor_column(), field_width);
            (
                inner_left
                    .saturating_add(prefix_width)
                    .saturating_add(cursor_column),
                inner_top,
            )
        }
        FocusField::Message => {
            let message_view_height = inner_area.height.saturating_sub(1) as usize;
            if message_view_height == 0 {
                return None;
            }
            let message_layout = compose_message_layout(
                state.message_field.as_str(),
                state.message_cursor_index(),
                inner_width as usize,
            );
            let start = compose_message_visible_start(
                message_layout.lines.len(),
                message_layout.cursor_row,
                message_view_height,
            );
            let cursor_row = message_layout
                .cursor_row
                .saturating_sub(start)
                .saturating_add(1);
            let cursor_column = visible_cursor_column_count(message_layout.cursor_col, inner_width);
            (
                inner_left.saturating_add(cursor_column),
                inner_top.saturating_add(cursor_row as u16),
            )
        }
    };

    Some((raw_x.min(inner_right), raw_y.min(inner_bottom)))
}

fn visible_cursor_column_count(count: usize, width: u16) -> u16 {
    if width == 0 {
        return 0;
    }
    (count as u16).min(width.saturating_sub(1))
}

fn interaction_raww_pane_area(area: Rect) -> Rect {
    let raww_height = INTERACTION_RAWW_PANE_HEIGHT.min(area.height.saturating_sub(2).max(1));
    let raww_y = area
        .y
        .saturating_add(area.height)
        .saturating_sub(raww_height);
    Rect {
        x: area.x,
        y: raww_y,
        width: area.width,
        height: raww_height,
    }
}