magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::super::{
    render_scroll_view_gutter_with_theme, render_text_scroll_view_with_theme,
    scroll_view_content_width, usize_to_u16_saturating,
};
use super::{SYSTEM_PROMPT_MODAL, draw_modal_panel_with_hotkeys, modal_text_area};
use crate::tui::{
    state::{MissionControlState, SystemPromptCard},
    theme::MissionControlTheme,
    transcript::ratatui_wrapped_visual_rows,
};
use ratatui::{
    Frame,
    layout::Rect,
    widgets::{Block, Borders, Paragraph, Wrap},
};

pub(crate) fn system_prompt_text_area(area: Rect) -> Option<Rect> {
    let text_area = modal_text_area(area, "Current System Prompt", SYSTEM_PROMPT_MODAL)?;
    (text_area.height > 0).then_some(text_area)
}

pub(in crate::tui::render) fn draw_system_prompt_modal(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(modal) = &state.modals.system_prompt_modal else {
        return;
    };
    let theme = state.theme;
    let Some(text_area) = draw_modal_panel_with_hotkeys(
        frame,
        area,
        "Current System Prompt",
        SYSTEM_PROMPT_MODAL,
        theme,
        "[↑/↓] scroll • [PgUp/PgDn] page • [Home/End] • [q] close",
        Some("[Esc] close"),
    ) else {
        return;
    };
    if text_area.is_empty() {
        return;
    }
    if modal.sections.is_empty() {
        render_text_scroll_view_with_theme(
            frame,
            text_area,
            &modal.content,
            &state.scroll_views.system_prompt,
            theme,
        );
        return;
    }

    let width = scroll_view_content_width(text_area);
    let height = usize_to_u16_saturating(modal.visual_rows(width)).max(text_area.height);
    let offset = state
        .scroll_views
        .system_prompt
        .borrow()
        .offset()
        .y
        .min(height.saturating_sub(text_area.height));
    let viewport = Rect { width, ..text_area };
    frame.render_widget(Block::default().style(theme.app()), viewport);

    let summary_rows = ratatui_wrapped_visual_rows(&modal.content, width.max(1));
    if usize::from(offset) < summary_rows {
        let summary_area = Rect {
            height: usize_to_u16_saturating(summary_rows - usize::from(offset))
                .min(viewport.height),
            ..viewport
        };
        frame.render_widget(
            Paragraph::new(modal.content.as_str())
                .style(theme.muted(false))
                .wrap(Wrap { trim: false })
                .scroll((offset, 0)),
            summary_area,
        );
    }
    let mut start = summary_rows.saturating_add(1);
    for card in &modal.sections {
        let rows = card.visual_rows(width);
        if start >= usize::from(offset) + usize::from(viewport.height) {
            break;
        }
        if start.saturating_add(rows) > usize::from(offset) {
            draw_card(
                frame,
                viewport,
                card,
                start,
                rows,
                usize::from(offset),
                theme,
            );
        }
        start = start.saturating_add(rows).saturating_add(1);
    }
    render_scroll_view_gutter_with_theme(frame, text_area, width, height, offset, None, theme);
}

/// Clip cards to the viewport; even a very large instruction file never gets a
/// document-sized cell buffer. Only visible paragraphs wrap their scrolled prefix.
fn draw_card(
    frame: &mut Frame<'_>,
    viewport: Rect,
    card: &SystemPromptCard,
    start: usize,
    rows: usize,
    offset: usize,
    theme: MissionControlTheme,
) {
    let end = start.saturating_add(rows);
    let visible_start = start.max(offset);
    let visible_end = end.min(offset + usize::from(viewport.height));
    let mut borders = Borders::LEFT | Borders::RIGHT;
    if visible_start == start {
        borders |= Borders::TOP;
    }
    if visible_end == end {
        borders |= Borders::BOTTOM;
    }
    let area = Rect::new(
        viewport.x,
        viewport.y + usize_to_u16_saturating(visible_start - offset),
        viewport.width,
        usize_to_u16_saturating(visible_end - visible_start),
    );
    frame.render_widget(
        Block::default()
            .borders(borders)
            .border_style(theme.border(false))
            .style(theme.app()),
        area,
    );
    let text_start = start.saturating_add(1);
    let visible_text_start = text_start.max(visible_start);
    let visible_text_end = end.saturating_sub(1).min(visible_end);
    if viewport.width <= 4 || visible_text_start >= visible_text_end {
        return;
    }
    let mut text = card.text();
    text.lines[0].style = theme.title(true);
    text.lines[1].style = theme.accent(true);
    frame.render_widget(
        Paragraph::new(text)
            .style(theme.pane())
            .wrap(Wrap { trim: false })
            .scroll((usize_to_u16_saturating(visible_text_start - text_start), 0)),
        Rect::new(
            viewport.x + 2,
            viewport.y + usize_to_u16_saturating(visible_text_start - offset),
            SystemPromptCard::text_width(viewport.width),
            usize_to_u16_saturating(visible_text_end - visible_text_start),
        ),
    );
}