magi-code 0.64.0

Repository-aware CLI coding agent for terminal work
Documentation
use super::{
    ACTIVE_LOGIN_MODAL, CONFIRMATION_MODAL, CUSTOM_PROVIDER_REPLACEMENT_MODAL,
    CUSTOM_PROVIDER_SETUP_MODAL, PICKER_MODAL, draw_modal_panel, picker_modal_list_area,
};
use crate::tui::{
    PADDED_INLINE_SEPARATOR,
    state::{AuthPickerMode, CustomProviderSetupStep, MissionControlState},
    theme::MATRIX_GREEN,
};
use ratatui::{
    Frame,
    layout::{Rect, Size},
    text::{Line, Span},
    widgets::{List, ListItem, ListState, Paragraph, Wrap},
};
use tui_scrollview::{ScrollView, ScrollbarVisibility};

pub(crate) fn auth_picker_list_area(area: Rect, has_notice: bool) -> Option<Rect> {
    let header_rows = 1 + u16::from(has_notice);
    picker_modal_list_area(area, "Login Providers", header_rows)
}

pub(in crate::tui::render) fn draw_login_picker(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(picker) = &state.modals.login_picker else {
        return;
    };
    let title = if picker.mode == AuthPickerMode::Logout {
        "Logout Providers"
    } else {
        "Login Providers"
    };
    let Some(text_area) = draw_modal_panel(frame, area, title, PICKER_MODAL) else {
        return;
    };
    if text_area.height == 0 {
        return;
    }
    let help = if picker.mode == AuthPickerMode::Logout {
        "[Enter] remove local auth  [Esc] cancel"
    } else {
        "[Enter] connect  [Esc] cancel"
    };
    frame.render_widget(
        Paragraph::new(super::super::hotkey_line(help, MATRIX_GREEN.muted(false)))
            .style(MATRIX_GREEN.muted(false)),
        Rect::new(text_area.x, text_area.y, text_area.width, 1),
    );
    let mut list_y = text_area.y.saturating_add(1);
    if let Some(notice) = &picker.notice
        && list_y < text_area.y.saturating_add(text_area.height)
    {
        frame.render_widget(
            Paragraph::new(notice.clone()).style(MATRIX_GREEN.muted(false)),
            Rect::new(text_area.x, list_y, text_area.width, 1),
        );
        list_y = list_y.saturating_add(1);
    }
    let list_height = text_area
        .y
        .saturating_add(text_area.height)
        .saturating_sub(list_y);
    if list_height == 0 {
        return;
    }
    let content_height =
        super::super::usize_to_u16_saturating(picker.entries.len().max(usize::from(list_height)));
    let content_width =
        super::super::scroll_view_content_width(Rect::new(0, 0, text_area.width, list_height));
    let mut view = ScrollView::new(Size::new(content_width, content_height))
        .vertical_scrollbar_visibility(ScrollbarVisibility::Always)
        .horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
    let items = picker
        .entries
        .iter()
        .map(|entry| {
            ListItem::new(Line::from(vec![Span::raw(format!(
                "{} ({}) [{}]{PADDED_INLINE_SEPARATOR}{}",
                entry.label, entry.id, entry.status, entry.description
            ))]))
        })
        .collect::<Vec<_>>();
    let mut list_state = ListState::default().with_selected(Some(picker.selected));
    view.render_stateful_widget(
        List::new(items)
            .style(MATRIX_GREEN.pane())
            .highlight_style(MATRIX_GREEN.selection())
            .highlight_symbol("> "),
        Rect::new(0, 0, content_width, content_height),
        &mut list_state,
    );
    frame.render_stateful_widget(
        view,
        Rect::new(text_area.x, list_y, text_area.width, list_height),
        &mut *state.scroll_views.login_picker.borrow_mut(),
    );
}

pub(in crate::tui::render) fn draw_logout_confirmation(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(confirmation) = &state.modals.logout_confirmation else {
        return;
    };
    let Some(text_area) = draw_modal_panel(frame, area, "Confirm Logout", CONFIRMATION_MODAL)
    else {
        return;
    };
    let lines = vec![
        Line::from(Span::raw(format!(
            "Remove local auth for {} ({})?",
            confirmation.label, confirmation.provider_id
        ))),
        Line::from(Span::raw("")),
        super::super::hotkey_line(
            "[Enter] remove local auth   [Esc] cancel",
            MATRIX_GREEN.pane(),
        ),
    ];
    frame.render_widget(
        Paragraph::new(lines)
            .style(MATRIX_GREEN.pane())
            .wrap(Wrap { trim: false }),
        text_area,
    );
}

pub(in crate::tui::render) fn draw_custom_provider_replacement_confirmation(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(confirmation) = &state.modals.custom_provider_replacement_confirmation else {
        return;
    };
    let Some(text_area) = draw_modal_panel(
        frame,
        area,
        "Replace Custom Provider",
        CUSTOM_PROVIDER_REPLACEMENT_MODAL,
    ) else {
        return;
    };
    let lines = vec![
        Line::from(Span::raw(format!(
            "Replace {} ({}) with new metadata?",
            confirmation.existing_label, confirmation.provider_id
        ))),
        Line::from(Span::raw("")),
        Line::from(Span::raw(format!("New label: {}", confirmation.label))),
        Line::from(Span::raw(format!(
            "New base URL: {}",
            confirmation.base_url
        ))),
        Line::from(Span::raw(format!(
            "API key: {}",
            confirmation
                .api_key_env_var
                .as_deref()
                .unwrap_or("not required")
        ))),
        Line::from(Span::raw("")),
        super::super::hotkey_line("[Enter] replace   [Esc] cancel", MATRIX_GREEN.pane()),
    ];
    frame.render_widget(
        Paragraph::new(lines)
            .style(MATRIX_GREEN.pane())
            .wrap(Wrap { trim: false }),
        text_area,
    );
}

pub(in crate::tui::render) fn draw_custom_provider_setup(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(setup) = &state.modals.custom_provider_setup else {
        return;
    };
    let Some(text_area) = draw_modal_panel(
        frame,
        area,
        "Set up custom provider",
        CUSTOM_PROVIDER_SETUP_MODAL,
    ) else {
        return;
    };
    if text_area.height == 0 {
        return;
    }
    let (step_count, step_label, prompt, help, input) = match setup.step {
        CustomProviderSetupStep::Label => (
            "1/3",
            "Provider label",
            "Label",
            "Type a readable name. Provider ID is derived automatically.",
            setup.label.as_str(),
        ),
        CustomProviderSetupStep::BaseUrl => (
            "2/3",
            "Base URL",
            "Base URL",
            "Use an API root URL (e.g. /v1, /v4). https:// is required except localhost http://.",
            setup.base_url.as_str(),
        ),
        CustomProviderSetupStep::ApiKeyEnvVar => (
            "3/3",
            "API key environment variable",
            "Env var",
            "Optional. Leave blank if this provider does not require an API key.",
            setup.api_key_env_var.as_str(),
        ),
    };
    let preview = setup
        .provider_id
        .as_deref()
        .map(|id| format!("Provider ID: {id}"))
        .unwrap_or_else(|| "Provider ID: derived after valid label".to_string());
    let api_mode = if setup.step == CustomProviderSetupStep::ApiKeyEnvVar
        && setup.api_key_env_var.trim().is_empty()
    {
        "Auth mode: API key not required; no Authorization header will be sent."
    } else {
        "Auth mode: env-var backed when a variable name is provided."
    };
    let mut lines = vec![
        Line::from(Span::raw(format!("Step {step_count} {step_label}"))),
        Line::from(Span::raw("")),
        Line::from(Span::raw(format!("{prompt}: {input}"))),
        Line::from(Span::raw(help)),
        Line::from(Span::raw(preview)),
    ];
    if matches!(setup.step, CustomProviderSetupStep::ApiKeyEnvVar) {
        lines.push(Line::from(Span::raw(api_mode)));
    }
    if let Some(error) = &setup.error {
        lines.push(Line::from(Span::raw("")));
        lines.push(Line::from(Span::raw(format!("Error: {error}"))));
    }
    lines.extend([
        Line::from(Span::raw("")),
        super::super::hotkey_line("[Enter] continue/save   [Esc] cancel", MATRIX_GREEN.pane()),
        Line::from(Span::raw(
            "Saved config stores metadata only; never enter an API-key value.",
        )),
    ]);
    frame.render_widget(
        Paragraph::new(lines)
            .style(MATRIX_GREEN.pane())
            .wrap(Wrap { trim: false }),
        text_area,
    );
}

pub(in crate::tui::render) fn draw_active_login(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(login) = &state.modals.active_login else {
        return;
    };
    let Some(text_area) = draw_modal_panel(frame, area, "OpenAI Codex Login", ACTIVE_LOGIN_MODAL)
    else {
        return;
    };
    if text_area.height == 0 {
        return;
    }
    let fallback_mask = if login.fallback_input.is_empty() {
        "<paste redirect URL or authorization code, then [Enter]>".to_string()
    } else {
        "<redacted input staged; press [Enter] to submit>".to_string()
    };
    let mut lines = vec![super::super::hotkey_line(
        "[Ctrl-Y] copy URL  [Ctrl-O] open browser  [Esc] cancel",
        MATRIX_GREEN.pane(),
    )];
    if login.url.is_empty() {
        lines.extend(
            login
                .instructions
                .lines()
                .map(|line| Line::from(Span::raw(line.to_string()))),
        );
    } else {
        lines.extend([
            super::super::hotkey_line(
                "Open this URL in your browser, or use [Ctrl-O].",
                MATRIX_GREEN.pane(),
            ),
            Line::from(Span::raw(
                "If needed, paste the redirect URL or authorization code below.",
            )),
        ]);
        lines.push(Line::from(Span::raw("")));
        lines.extend(wrapped_prefixed_lines(
            "URL: ",
            &login.url,
            text_area.width.max(1),
        ));
    }
    lines.extend([
        Line::from(Span::raw("")),
        Line::from(Span::raw(
            "Manual fallback input is transient and redacted.",
        )),
        super::super::hotkey_line(fallback_mask, MATRIX_GREEN.pane()),
        super::super::hotkey_line(
            "[Enter] submit fallback  [Esc] cancel without writing credentials",
            MATRIX_GREEN.pane(),
        ),
    ]);
    frame.render_widget(
        Paragraph::new(lines)
            .style(MATRIX_GREEN.pane())
            .wrap(Wrap { trim: false }),
        text_area,
    );
}

fn wrapped_prefixed_lines(prefix: &str, text: &str, width: u16) -> Vec<Line<'static>> {
    let width = width.max(1) as usize;
    let prefix_len = prefix.chars().count();
    let first_chunk_len = width.saturating_sub(prefix_len).max(1);
    let mut chunks = text.chars();
    let mut lines = Vec::new();
    let first = chunks.by_ref().take(first_chunk_len).collect::<String>();
    lines.push(Line::from(Span::raw(format!("{prefix}{first}"))));
    loop {
        let chunk = chunks.by_ref().take(width).collect::<String>();
        if chunk.is_empty() {
            break;
        }
        lines.push(Line::from(Span::raw(chunk)));
    }
    lines
}