magi-code 0.102.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::{
    PICKER_MODAL, draw_modal_panel_with_hotkeys, picker_list_area, picker_modal_list_area,
    render_picker_list,
};
use crate::tui::state::MissionControlState;
use ratatui::{
    Frame,
    layout::Rect,
    text::Line,
    widgets::{ListItem, Paragraph, Wrap},
};

fn split_body(body: Rect) -> (Rect, Rect) {
    let list_height = (body.height / 3).max(1).min(body.height);
    (
        Rect::new(body.x, body.y, body.width, list_height),
        Rect::new(
            body.x,
            body.y + list_height,
            body.width,
            body.height - list_height,
        ),
    )
}

pub(crate) fn mcp_modal_list_area(area: Rect) -> Option<Rect> {
    picker_modal_list_area(area, "MCP Servers", 1).map(|body| split_body(body).0)
}

pub(crate) fn mcp_definition_area(area: Rect) -> Option<Rect> {
    picker_modal_list_area(area, "MCP Servers", 1).map(|body| split_body(body).1)
}

pub(in crate::tui::render) fn draw_mcp_modal(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(m) = &state.modals.mcp_modal else {
        return;
    };
    let theme = state.theme;
    let Some(a) = draw_modal_panel_with_hotkeys(
        frame,
        area,
        "MCP Servers",
        PICKER_MODAL,
        theme,
        "[↑/↓] server • [PgUp/PgDn] inspect • [Enter] approve/revoke",
        Some("[Esc] close"),
    ) else {
        return;
    };
    if a.height == 0 {
        return;
    }
    frame.render_widget(
        Paragraph::new("Approvals apply next launch; inspect current definition below.")
            .style(theme.muted(false)),
        Rect::new(a.x, a.y, a.width, 1),
    );
    let Some(body) = picker_list_area(a, 1) else {
        return;
    };
    if m.rows.is_empty() {
        frame.render_widget(
            Paragraph::new(vec![
                Line::from("No MCP servers configured."),
                Line::from("Add definitions to CONFIG_DIR/.mcp.json or cwd/.mcp.json"),
            ])
            .style(theme.pane()),
            body,
        );
        return;
    }
    let (list_area, definition_area) = split_body(body);
    let items = m
        .rows
        .iter()
        .map(|r| {
            ListItem::new(Line::from(format!(
                "{} {} ({}): {}",
                if r.enabled { "[x]" } else { "[ ]" },
                r.name,
                r.kind,
                r.snapshot.status
            )))
        })
        .collect::<Vec<_>>();
    render_picker_list(
        frame,
        list_area,
        items,
        m.selected,
        &state.scroll_views.mcp_modal,
        theme,
    );
    if let Some(row) = m.rows.get(m.selected) {
        let paragraph = Paragraph::new(row.definition_display.as_str())
            .wrap(Wrap { trim: false })
            .style(theme.pane());
        let max = paragraph
            .line_count(definition_area.width.max(1))
            .saturating_sub(usize::from(definition_area.height));
        let offset = usize::from(m.definition_scroll)
            .min(max)
            .min(usize::from(u16::MAX)) as u16;
        frame.render_widget(paragraph.scroll((offset, 0)), definition_area);
    }
}