magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;

#[derive(Debug, Clone, Copy)]
pub(super) struct DisplayWidthUnit<'a> {
    pub(super) text: &'a str,
    pub(super) end: usize,
    pub(super) width: usize,
}

pub(super) fn next_display_width_unit(text: &str, start: usize) -> Option<DisplayWidthUnit<'_>> {
    let (offset, unit) = text[start..]
        .grapheme_indices(true)
        .find(|(_, grapheme)| !grapheme.chars().any(char::is_control))?;
    let end = start + offset + unit.len();
    Some(DisplayWidthUnit {
        text: unit,
        end,
        width: UnicodeWidthStr::width(unit),
    })
}

pub(in crate::tui::transcript_cards) fn spans_text(spans: &[Span<'static>]) -> String {
    spans
        .iter()
        .map(|span| span.content.as_ref())
        .collect::<String>()
}

pub(in crate::tui::transcript_cards) fn spans_display_width(spans: &[Span<'static>]) -> usize {
    spans
        .iter()
        .map(|span| UnicodeWidthStr::width(span.content.as_ref()))
        .sum()
}

pub(super) fn transcript_gap_visual_line(
    width: u16,
    theme: MissionControlTheme,
) -> TranscriptVisualLine {
    TranscriptVisualLine {
        image_row: None,
        status_indicators: Vec::new(),
        line: Line::from(Span::styled(
            " ".repeat(usize::from(width)),
            theme.pane().bg(theme.surface_background()),
        )),
        copy_text: String::new(),
        copy_byte_range: None,
        copyable: true,
        copy_continuation: false,
        visual_copy_column_offset: 0,
        visual_copy_byte_boundaries: Vec::new(),
        hit_regions: Vec::new(),
    }
}

pub(super) fn transcript_bottom_padding_visual_line(
    width: u16,
    theme: MissionControlTheme,
) -> TranscriptVisualLine {
    let mut line = transcript_gap_visual_line(width, theme);
    line.copyable = false;
    line
}

pub(super) fn truncate_to_display_width(text: &str, max_width: usize) -> String {
    let mut out = String::new();
    let mut width = 0usize;
    let mut index = 0usize;
    while let Some(unit) = next_display_width_unit(text, index) {
        if width.saturating_add(unit.width) > max_width {
            break;
        }
        out.push_str(unit.text);
        width = width.saturating_add(unit.width);
        index = unit.end;
    }
    out
}

pub(super) fn pad_full_width_spans(
    mut spans: Vec<Span<'static>>,
    width: u16,
) -> Vec<Span<'static>> {
    let used = spans
        .iter()
        .map(|span| UnicodeWidthStr::width(span.content.as_ref()))
        .sum::<usize>();
    let style = spans.last().map(|span| span.style).unwrap_or_default();
    let padding = usize::from(width).saturating_sub(used);
    if padding > 0 {
        spans.push(Span::styled(" ".repeat(padding), style));
    }
    spans
}

pub(super) fn card_header_background(
    role: TranscriptCardRole,
    theme: MissionControlTheme,
) -> ratatui::style::Color {
    if role == TranscriptCardRole::Error {
        theme.surface_error()
    } else {
        theme.surface_panel_alt()
    }
}

pub(super) fn status_header_style(
    status: TranscriptCardStatus,
    theme: MissionControlTheme,
) -> Style {
    match status {
        TranscriptCardStatus::Running => Style::new()
            .fg(theme.activity_status_color(ActivityStatus::Running))
            .add_modifier(Modifier::BOLD),
        TranscriptCardStatus::Success => {
            Style::new().fg(theme.activity_status_color(ActivityStatus::Success))
        }
        TranscriptCardStatus::Failed => Style::new()
            .fg(theme.activity_status_color(ActivityStatus::Failed))
            .add_modifier(Modifier::BOLD),
        TranscriptCardStatus::Canceled => Style::new()
            .fg(theme.activity_status_color(ActivityStatus::Canceled))
            .add_modifier(Modifier::DIM),
        TranscriptCardStatus::None => theme.muted(false),
    }
}

pub(super) fn role_label_style(
    role: TranscriptCardRole,
    active: bool,
    theme: MissionControlTheme,
) -> Style {
    match role {
        TranscriptCardRole::Error => theme.transcript_error_label(),
        TranscriptCardRole::Session
        | TranscriptCardRole::Diagnostic
        | TranscriptCardRole::Compaction
        | TranscriptCardRole::Empty => theme.muted(active),
        TranscriptCardRole::Bash => theme.accent(active).add_modifier(Modifier::BOLD),
        TranscriptCardRole::Reasoning => theme.dim(active).add_modifier(Modifier::BOLD),
        TranscriptCardRole::Tool
        | TranscriptCardRole::CodeMode
        | TranscriptCardRole::MagiControl
        | TranscriptCardRole::HashEdit
        | TranscriptCardRole::Write
        | TranscriptCardRole::ViewImage
        | TranscriptCardRole::Read
        | TranscriptCardRole::Grep
        | TranscriptCardRole::AstGrep
        | TranscriptCardRole::Find
        | TranscriptCardRole::ListFiles
        | TranscriptCardRole::Web
        | TranscriptCardRole::SubagentBatch
        | TranscriptCardRole::UserPrompt
        | TranscriptCardRole::Assistant => theme.accent(active).add_modifier(Modifier::BOLD),
    }
}

pub(super) fn role_body_style(
    role: TranscriptCardRole,
    _active: bool,
    theme: MissionControlTheme,
) -> Style {
    match role {
        TranscriptCardRole::UserPrompt | TranscriptCardRole::Bash => theme.transcript_user_body(),
        TranscriptCardRole::Tool
        | TranscriptCardRole::CodeMode
        | TranscriptCardRole::MagiControl
        | TranscriptCardRole::HashEdit
        | TranscriptCardRole::Write
        | TranscriptCardRole::ViewImage
        | TranscriptCardRole::Read
        | TranscriptCardRole::Grep
        | TranscriptCardRole::AstGrep
        | TranscriptCardRole::Find
        | TranscriptCardRole::ListFiles
        | TranscriptCardRole::Web
        | TranscriptCardRole::SubagentBatch => theme.transcript_tool_body(),
        TranscriptCardRole::Error => theme.transcript_error_body(),
        TranscriptCardRole::Session
        | TranscriptCardRole::Diagnostic
        | TranscriptCardRole::Compaction => theme.transcript_session_body(),
        TranscriptCardRole::Reasoning => theme.dim(false),
        TranscriptCardRole::Assistant | TranscriptCardRole::Empty => theme.pane(),
    }
}

pub(super) fn card_body_background(
    role: TranscriptCardRole,
    theme: MissionControlTheme,
) -> ratatui::style::Color {
    if role == TranscriptCardRole::Error {
        theme.surface_error()
    } else {
        theme.surface_panel()
    }
}

pub(super) fn display_status_mark(card: &TranscriptCard) -> &'static str {
    match card.role {
        TranscriptCardRole::Assistant
        | TranscriptCardRole::UserPrompt
        | TranscriptCardRole::Reasoning
        | TranscriptCardRole::SubagentBatch => "",
        _ => status_mark(card.status),
    }
}

pub(in crate::tui::transcript_cards) fn status_mark(status: TranscriptCardStatus) -> &'static str {
    match status {
        TranscriptCardStatus::Running => activity_status_mark(ActivityStatus::Running),
        TranscriptCardStatus::Success => activity_status_mark(ActivityStatus::Success),
        TranscriptCardStatus::Failed => activity_status_mark(ActivityStatus::Failed),
        TranscriptCardStatus::Canceled => activity_status_mark(ActivityStatus::Canceled),
        TranscriptCardStatus::None => "",
    }
}