magi-code 0.80.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::theme::MissionControlTheme;
use ratatui::{
    Frame,
    layout::Rect,
    style::{Modifier, Style},
    text::{Line, Span},
    widgets::{Block, BorderType, Borders, Clear},
};

/// Normal modals stay within 30–80% of each available dimension.
pub(crate) fn modal_area(area: Rect, width_percent: u16, height_percent: u16) -> Rect {
    compact_modal_area(
        area,
        width_percent.clamp(30, 80),
        height_percent.clamp(30, 80),
    )
}

/// Explicit exception for compact dialogs; percentages still cannot exceed the parent.
pub(crate) fn compact_modal_area(area: Rect, width_percent: u16, height_percent: u16) -> Rect {
    let width = (u32::from(area.width) * u32::from(width_percent.min(100)) / 100) as u16;
    let height = (u32::from(area.height) * u32::from(height_percent.min(100)) / 100) as u16;
    Rect::new(
        area.x.saturating_add((area.width - width) / 2),
        area.y.saturating_add((area.height - height) / 2),
        width,
        height,
    )
}

/// `close_hint` is None when closing is disabled, or a truthful custom hint.
pub(crate) fn modal_block<'a>(
    title: impl Into<Line<'a>>,
    hotkeys: impl Into<Line<'a>>,
    theme: MissionControlTheme,
    close_hint: Option<&'a str>,
) -> Block<'a> {
    let mut block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Plain)
        .style(theme.app())
        .title_top(title.into().left_aligned())
        .title_bottom(hotkeys.into().left_aligned());
    if let Some(hint) = close_hint {
        block = block.title_top(Line::from(hint).right_aligned());
    }
    block
}

/// Clears and paints the shell, returning its unpadded content rectangle.
pub(crate) fn draw_modal_container<'a>(
    frame: &mut Frame<'_>,
    area: Rect,
    title: impl Into<Line<'a>>,
    hotkeys: impl Into<Line<'a>>,
    theme: MissionControlTheme,
    close_hint: Option<&'a str>,
) -> Rect {
    let block = modal_block(title, hotkeys, theme, close_hint);
    let inner = block.inner(area);
    frame.render_widget(Clear, area);
    frame.render_widget(block, area);
    inner
}

/// Selected Settings-style tab; inactive tabs (including future steps) stay muted.
pub(crate) fn modal_tab(label: &str, selected: bool, theme: MissionControlTheme) -> Span<'static> {
    if selected {
        Span::styled(
            format!("[{label}]"),
            Style::default()
                .fg(theme.text_accent())
                .bg(theme.surface_selection())
                .add_modifier(Modifier::BOLD),
        )
    } else {
        Span::styled(format!(" {label} "), theme.muted(false))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::{Terminal, backend::TestBackend};

    #[test]
    fn normal_geometry_clamps_and_centers_in_offset_parent() {
        let parent = Rect::new(10, 20, 100, 100);
        assert_eq!(modal_area(parent, 60, 60), Rect::new(30, 40, 60, 60));
        assert_eq!(modal_area(parent, 0, 100), Rect::new(45, 30, 30, 80));
        assert_eq!(
            compact_modal_area(parent, 40, 20),
            Rect::new(40, 60, 40, 20)
        );
        for width in 0..12 {
            for height in 0..12 {
                let parent = Rect::new(3, 5, width, height);
                let modal = modal_area(parent, 60, 60);
                assert_eq!(modal.intersection(parent), modal);
            }
        }
    }

    #[test]
    fn shell_places_chrome_on_borders_and_clears_content() {
        let theme = MissionControlTheme::default();
        let mut terminal = Terminal::new(TestBackend::new(50, 12)).unwrap();
        for close_hint in [Some("[Esc] close"), None] {
            terminal
                .draw(|frame| {
                    draw_modal_container(
                        frame,
                        frame.area(),
                        "Picker",
                        "[Enter] select",
                        theme,
                        close_hint,
                    );
                })
                .unwrap();
            let buffer = terminal.backend().buffer();
            let row = |y| (0..50).map(|x| buffer[(x, y)].symbol()).collect::<String>();
            assert!(row(0).starts_with("┌Picker"));
            assert_eq!(row(0).contains("[Esc] close"), close_hint.is_some());
            assert!(row(11).contains("[Enter] select"));
            assert!(!row(1).contains("select"));
            assert_eq!(buffer[(25, 5)].bg, theme.app().bg.unwrap());
        }
    }
}