magi-code 0.77.1

Repository-aware CLI coding agent for terminal work
Documentation
use crate::tui::{
    state::MissionControlState,
    usage_format::{USAGE_SEPARATOR, compact_tokens, usage_metric_spans},
};
#[cfg(test)]
use crate::{
    output::NormalizedUsageSnapshot, tui::usage_format::usage_fields as session_usage_fields,
};
use ratatui::{
    Frame,
    layout::Rect,
    text::{Line, Span},
    widgets::Paragraph,
};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

pub(super) fn draw_prompt_footer(frame: &mut Frame<'_>, area: Rect, state: &MissionControlState) {
    if area.width <= 2 || area.height < 2 {
        return;
    }
    let available = usize::from(area.width - 2);
    let full_left = footer_text(state, true);
    let right = super::super::header::version_title(state);
    // Independent rectangles prevent opposing border labels from colliding.
    let right_width = right.width().min(available / 2);
    let left_width = available.saturating_sub(right_width + usize::from(right_width > 0));
    let left = if full_left.width() <= left_width {
        full_left
    } else {
        footer_text(state, false)
    };
    let y = area.bottom() - 1;
    frame.render_widget(
        Paragraph::new(truncate_line(left, left_width)),
        Rect::new(area.x + 1, y, left_width as u16, 1),
    );
    frame.render_widget(
        Paragraph::new(truncate_line(right, right_width)).right_aligned(),
        Rect::new(
            area.right() - 1 - right_width as u16,
            y,
            right_width as u16,
            1,
        ),
    );
}

fn footer_text(state: &MissionControlState, show_bar: bool) -> Line<'static> {
    let auto = &state.auto_compaction;
    let policy = state
        .context_usage
        .and_then(|usage| crate::agent::runner::auto_compaction_policy(auto, usage.max_tokens));
    let auto_label = auto_compaction_label(auto, state.context_usage.map(|usage| usage.max_tokens));
    let usage = state.context_usage;
    let context = usage.map_or_else(
        || "—%".to_string(),
        |usage| context_usage_label(usage.current_tokens, usage.max_tokens),
    );
    let muted = state.theme.muted(state.is_prompt_focused());
    let session = usage_metric_spans(
        state.session_usage_totals(),
        state.session_cache_percent(),
        muted,
        state.theme,
    );
    let mut spans = vec![Span::styled(" Session: ", muted)];
    for (index, field) in session.into_iter().enumerate() {
        if index > 0 {
            spans.push(Span::styled(USAGE_SEPARATOR, muted));
        }
        spans.push(field);
    }
    spans.push(Span::styled(" • Current Ctx: ", muted));
    spans.push(Span::styled(context, muted.fg(state.theme.text_status())));
    spans.push(Span::styled(" ", muted));
    if show_bar {
        let bar = context_bar(
            usage.map_or(0, |usage| usage.current_tokens),
            usage.map_or(0, |usage| usage.max_tokens),
            policy.as_ref().map(|(tokens, _)| *tokens),
        );
        spans.push(Span::styled(bar, muted.fg(state.theme.text_accent())));
        spans.push(Span::styled(" ", muted));
    }
    let total = usage.map_or_else(
        || "".to_string(),
        |usage| compact_tokens(usage.max_tokens as u64),
    );
    spans.push(Span::styled(total, muted.fg(state.theme.text_status())));
    spans.push(Span::styled("", muted));
    spans.push(Span::styled(
        auto_label,
        muted.fg(state.theme.text_command()),
    ));
    spans.push(Span::styled(" ", muted));
    Line::from(spans)
}

fn auto_compaction_label(
    auto: &crate::config::AutoCompactionSettings,
    max_tokens: Option<usize>,
) -> String {
    if !auto.enabled {
        return "Auto off".to_string();
    }
    let threshold = match (auto.threshold_percent, auto.threshold_tokens, max_tokens) {
        (Some(percent), Some(tokens), Some(max_tokens)) => {
            let percent_tokens = (max_tokens as u128 * u128::from(percent)).div_ceil(100);
            if u128::from(tokens) < percent_tokens {
                format!("{} tokens", compact_tokens(tokens))
            } else {
                format!("{percent}%")
            }
        }
        (Some(percent), None, _) => format!("{percent}%"),
        (None, Some(tokens), _) => format!("{} tokens", compact_tokens(tokens)),
        _ => match auto.threshold_display() {
            Some(threshold) => threshold,
            None => return "Auto off".to_string(),
        },
    };
    format!("Compact at {threshold}")
}

fn context_usage_label(current_tokens: usize, max_tokens: usize) -> String {
    let percent = if max_tokens == 0 {
        "".to_string()
    } else {
        ((current_tokens as u128 * 100 / max_tokens as u128).min(999)).to_string()
    };
    format!("{percent}%")
}

fn context_bar(current_tokens: usize, max_tokens: usize, threshold: Option<usize>) -> String {
    const CELLS: usize = 10;
    let filled = if max_tokens == 0 {
        0
    } else {
        ((current_tokens as u128 * CELLS as u128 / max_tokens as u128).min(CELLS as u128)) as usize
    };
    let marker = threshold.filter(|_| max_tokens > 0).map(|tokens| {
        ((tokens as u128 * CELLS as u128 / max_tokens as u128).min((CELLS - 1) as u128)) as usize
    });
    let mut bar = String::from("[");
    for index in 0..CELLS {
        bar.push(if marker == Some(index) {
            '|'
        } else if index < filled {
            '#'
        } else {
            '.'
        });
    }
    bar.push(']');
    bar
}

pub(super) fn truncate_line(line: Line<'static>, width: usize) -> Line<'static> {
    if line.width() <= width {
        return line;
    }
    if width == 0 {
        return Line::default();
    }
    let mut remaining = width - 1;
    let mut spans = Vec::new();
    'spans: for span in line.spans {
        let mut text = String::new();
        for grapheme in span.content.graphemes(true) {
            let cells = grapheme.width();
            if cells > remaining {
                spans.push(Span::styled(text, span.style));
                break 'spans;
            }
            text.push_str(grapheme);
            remaining -= cells;
        }
        spans.push(Span::styled(text, span.style));
    }
    spans.push(Span::raw(""));
    Line::from(spans).style(line.style)
}

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

    fn footer_state() -> MissionControlState {
        MissionControlState {
            provider_ready: true,
            provider: "provider".to_string(),
            model: "model".to_string(),
            context_usage: Some(crate::tui::state::ContextUsageState {
                current_tokens: 124_000,
                max_tokens: 200_000,
                reasoning_tokens: None,
                source: crate::output::ContextUsageSource::ProviderExact,
                request_sequence: 1,
            }),
            auto_compaction: crate::config::AutoCompactionSettings {
                enabled: true,
                threshold_percent: Some(80),
                ..Default::default()
            },
            ..Default::default()
        }
    }

    fn footer_row(width: u16, state: &MissionControlState) -> String {
        let mut terminal = Terminal::new(TestBackend::new(width, 6)).unwrap();
        terminal
            .draw(|frame| super::super::draw_prompt(frame, frame.area(), state))
            .unwrap();
        (0..width)
            .map(|x| terminal.backend().buffer()[(x, 5)].symbol())
            .collect()
    }

    #[test]
    fn wide_footer_places_usage_and_full_context_left_of_version() {
        let row = footer_row(160, &footer_state());
        assert!(
            row.contains(" Session:    0(in)│   0(out)│   —(cache) • Current Ctx: 62% [######..|.] 200k • Compact at 80% "),
            "{row}"
        );
        assert!(
            row.contains(&format!("MAGI-CODE v{}", env!("CARGO_PKG_VERSION"))),
            "{row}"
        );
        assert!(row.find("Compact at 80%").unwrap() < row.find("MAGI-CODE").unwrap());
        assert!(!row.contains("[Alt-M]"));
        assert!(!row.contains("Prompt [Alt-P]"));
    }

    #[test]
    fn narrow_footer_reserves_version_space_and_clips_without_overlap() {
        let state = footer_state();
        let row = footer_row(80, &state);
        assert!(
            row.contains("Session:    0(in)│   0(out)│   —(cache) • Current Ctx: 62%…"),
            "{row}"
        );
        assert!(
            row.ends_with(&format!("MAGI-CODE v{}", env!("CARGO_PKG_VERSION"))),
            "{row}"
        );
        for width in [0, 1, 2, 3, 12, 20, 40, 60] {
            let row = footer_row(width, &state);
            assert_eq!(row.width(), usize::from(width));
            if width >= 3 {
                assert!(row.ends_with(''), "{width}: {row}");
            }
        }
        let row = footer_row(40, &state);
        assert!(row.contains(''), "{row}");
        assert!(
            row.ends_with(&format!("MAGI-CODE v{}", env!("CARGO_PKG_VERSION"))),
            "{row}"
        );
    }

    #[test]
    fn cache_display_distinguishes_unknown_from_zero_and_retains_last_known_value() {
        let mut state = footer_state();
        assert!(footer_row(160, &state).contains("   —(cache)"));
        state.last_known_session_cache_percent = Some(76);
        assert!(footer_row(160, &state).contains(" 76%(cache)"));
        state.last_known_session_cache_percent = Some(0);
        assert!(footer_row(160, &state).contains("  0%(cache)"));
    }

    #[test]
    fn usage_fields_keep_their_width_across_updates() {
        let mut state = footer_state();
        state.context_usage = None;
        assert!(
            footer_text(&state, true)
                .to_string()
                .contains("Current Ctx: —% [..........] —")
        );
        for tokens in [
            0,
            9,
            99,
            999,
            1_000,
            9_000,
            99_000,
            999_499,
            999_999,
            1_000_000,
            99_000_000,
            999_000_000,
            9_000_000_000,
            99_000_000_000,
            999_000_000_000,
            u64::MAX,
        ] {
            let usage = NormalizedUsageSnapshot {
                effective_input: tokens,
                output: tokens,
                cache_read: tokens,
                cache_known: true,
            };
            for cache in [None, Some(0), Some(76), Some(100)] {
                let label = session_usage_fields(usage, cache).join("");
                assert_eq!(label.width(), 30, "{label}");
                assert!(!label.contains("partial"));
            }
        }
    }

    #[test]
    fn footer_uses_theme_colors_for_each_metric() {
        let state = footer_state();
        let mut terminal = Terminal::new(TestBackend::new(160, 6)).unwrap();
        terminal
            .draw(|frame| super::super::draw_prompt(frame, frame.area(), &state))
            .unwrap();
        let buffer = terminal.backend().buffer();
        let row = footer_row(160, &state);
        for (label, color) in [
            ("0(in)", state.theme.transcript_user_color()),
            ("0(out)", state.theme.text_accent()),
            (
                "—(cache)",
                state
                    .theme
                    .activity_status_color(crate::output::ActivityStatus::Success),
            ),
            ("[######", state.theme.text_accent()),
            ("62%", state.theme.text_status()),
        ] {
            let byte = row.find(label).unwrap();
            let x = row[..byte].width() as u16;
            assert_eq!(buffer[(x, 5)].fg, color, "{label}");
        }
    }

    #[test]
    fn bar_retains_configured_marker_before_at_and_after_threshold() {
        assert_eq!(context_bar(124_000, 200_000, Some(160_000)), "[######..|.]");
        assert_eq!(context_bar(160_000, 200_000, Some(160_000)), "[########|.]");
        assert_eq!(context_bar(180_000, 200_000, Some(160_000)), "[########|.]");
        assert_eq!(context_bar(240_000, 200_000, Some(160_000)), "[########|#]");
        assert_eq!(context_usage_label(240_000, 200_000), "120%");
        assert_eq!(context_bar(0, 200_000, Some(200_000)), "[.........|]");
        assert_eq!(context_bar(0, 200_000, Some(0)), "[|.........]");
        assert_eq!(context_bar(0, 0, Some(0)), "[..........]");
        assert_eq!(context_usage_label(0, 0), "—%");
    }

    #[test]
    fn auto_label_and_marker_choose_earliest_configured_threshold() {
        let mut state = footer_state();
        state.auto_compaction.threshold_tokens = Some(100_000);
        let text = footer_text(&state, true).to_string();
        assert!(
            text.contains("Current Ctx: 62% [#####|....] 200k • Compact at 100k tokens"),
            "{text}"
        );
        state.auto_compaction.threshold_tokens = Some(180_000);
        let text = footer_text(&state, true).to_string();
        assert!(
            text.contains("Current Ctx: 62% [######..|.] 200k • Compact at 80%"),
            "{text}"
        );
        state.auto_compaction.enabled = false;
        let text = footer_text(&state, true).to_string();
        assert!(
            text.contains("Current Ctx: 62% [######....] 200k • Auto off"),
            "{text}"
        );
        let auto = crate::config::AutoCompactionSettings {
            enabled: true,
            threshold_percent: Some(50),
            threshold_tokens: Some(501),
            ..Default::default()
        };
        assert_eq!(auto_compaction_label(&auto, Some(1001)), "Compact at 50%");
    }

    #[test]
    fn clipping_preserves_unicode_cell_boundaries() {
        let clipped = truncate_line(Line::from("ab界cd"), 4);
        assert_eq!(clipped.to_string(), "ab…");
        assert!(clipped.width() <= 4);
    }
}