magi-code 0.96.1

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

pub(super) fn push_text_line(
    lines: &mut Vec<TranscriptVisualLine>,
    card: &TranscriptCard,
    text: String,
    style: Style,
    is_body: bool,
    width: u16,
    body_style: Style,
) {
    push_wide_spans(
        lines,
        card,
        vec![Span::styled(text, style)],
        is_body,
        width,
        body_style,
    );
}

fn push_wide_spans(
    lines: &mut Vec<TranscriptVisualLine>,
    _card: &TranscriptCard,
    spans: Vec<Span<'static>>,
    _is_body: bool,
    width: u16,
    edge_style: Style,
) {
    for (index, row_spans) in wrap_spans_to_width(spans, body_wrap_width(width))
        .into_iter()
        .enumerate()
    {
        let copy_text = spans_text(&row_spans);
        lines.push(TranscriptVisualLine {
            image_row: None,
            status_indicators: Vec::new(),
            line: wide_body_line(row_spans, width, edge_style),
            copy_text,
            copy_byte_range: None,
            hit_regions: Vec::new(),
            copyable: true,
            copy_continuation: index > 0,
            visual_copy_column_offset: body_line_copy_offset(width, 0),
            visual_copy_byte_boundaries: Vec::new(),
        });
    }
}

#[derive(Debug, Clone)]
pub(super) struct AssistantVisualSpan {
    pub(super) text: String,
    pub(super) style: Style,
    pub(super) role: Option<DisplayRole>,
}

impl AssistantVisualSpan {
    pub(super) fn styled<T: Into<String>>(
        text: T,
        style: Style,
        role: Option<DisplayRole>,
    ) -> Self {
        Self {
            text: text.into(),
            style,
            role,
        }
    }

    fn into_ratatui_span(self) -> Span<'static> {
        Span::styled(self.text, self.style)
    }
}

pub(super) fn push_wide_assistant_spans(
    lines: &mut Vec<TranscriptVisualLine>,
    _card: &TranscriptCard,
    spans: Vec<AssistantVisualSpan>,
    _is_body: bool,
    width: u16,
    edge_style: Style,
) {
    for (index, row_spans) in wrap_assistant_spans_to_width(spans, body_wrap_width(width))
        .into_iter()
        .enumerate()
    {
        let copy_text = assistant_wide_copy_text(&row_spans);
        let gutter = assistant_code_gutter_width(&row_spans);
        lines.push(TranscriptVisualLine {
            image_row: None,
            status_indicators: Vec::new(),
            line: wide_body_line(
                row_spans
                    .into_iter()
                    .map(AssistantVisualSpan::into_ratatui_span)
                    .collect(),
                width,
                edge_style,
            ),
            copy_text,
            copy_byte_range: None,
            copyable: true,
            hit_regions: Vec::new(),
            copy_continuation: index > 0,
            visual_copy_column_offset: body_line_copy_offset(width, gutter),
            visual_copy_byte_boundaries: Vec::new(),
        });
    }
}

pub(super) fn assistant_wide_copy_text(spans: &[AssistantVisualSpan]) -> String {
    spans
        .iter()
        .filter(|span| span.role != Some(DisplayRole::CodeBlockGutter))
        .map(|span| span.text.as_str())
        .collect()
}

fn assistant_code_gutter_width(spans: &[AssistantVisualSpan]) -> u16 {
    display_width_to_u16(
        spans
            .iter()
            .filter(|span| span.role == Some(DisplayRole::CodeBlockGutter))
            .map(|span| UnicodeWidthStr::width(span.text.as_str()))
            .sum(),
    )
}

pub(super) fn display_width_to_u16(width: usize) -> u16 {
    width.min(usize::from(u16::MAX)) as u16
}

pub(super) fn wide_body_line(
    content_spans: Vec<Span<'static>>,
    width: u16,
    body_style: Style,
) -> Line<'static> {
    padded_body_line(content_spans, width, body_style, horizontal_padding(width))
}

pub(super) fn padded_body_line(
    mut content_spans: Vec<Span<'static>>,
    width: u16,
    body_style: Style,
    (left, right): (usize, usize),
) -> Line<'static> {
    for span in &mut content_spans {
        span.style = span
            .style
            .bg(body_style.bg.unwrap_or(ratatui::style::Color::Reset));
    }
    let content_width = spans_display_width(&content_spans);
    let available = usize::from(width)
        .saturating_sub(left)
        .saturating_sub(right);
    let padding = available.saturating_sub(content_width);
    let mut result = Vec::new();
    if left > 0 {
        result.push(Span::styled(" ".repeat(left), body_style));
    }
    result.extend(content_spans);
    if padding.saturating_add(right) > 0 {
        result.push(Span::styled(
            " ".repeat(padding.saturating_add(right)),
            body_style,
        ));
    }
    Line::from(result)
}

pub(in crate::tui::transcript_cards) fn wrap_spans_to_width(
    spans: Vec<Span<'static>>,
    max_width: usize,
) -> Vec<Vec<Span<'static>>> {
    let max_width = max_width.max(1);
    let mut rows = Vec::new();
    let mut row = Vec::new();
    let mut row_width = 0usize;

    for span in spans {
        let style = span.style;
        let mut segment = String::new();
        let mut index = 0usize;
        let content = span.content.as_ref();
        while let Some(unit) = next_display_width_unit(content, index) {
            index = unit.end;
            let unit_width = unit.width;
            if row_width.saturating_add(unit_width) > max_width
                && (!row.is_empty() || !segment.is_empty())
            {
                if !segment.is_empty() {
                    row.push(Span::styled(std::mem::take(&mut segment), style));
                }
                rows.push(std::mem::take(&mut row));
                row_width = 0;
            }
            segment.push_str(unit.text);
            row_width = row_width.saturating_add(unit_width);
            if row_width >= max_width {
                row.push(Span::styled(std::mem::take(&mut segment), style));
                rows.push(std::mem::take(&mut row));
                row_width = 0;
            }
        }
        if !segment.is_empty() {
            row.push(Span::styled(segment, style));
        }
    }

    if rows.is_empty() || !row.is_empty() {
        rows.push(row);
    }
    rows
}

pub(super) fn wrap_assistant_spans_to_width(
    spans: Vec<AssistantVisualSpan>,
    max_width: usize,
) -> Vec<Vec<AssistantVisualSpan>> {
    let max_width = max_width.max(1);
    let mut rows = Vec::new();
    let mut row = Vec::new();
    let mut row_width = 0usize;

    for span in spans {
        let style = span.style;
        let role = span.role;
        let mut segment = String::new();
        let mut index = 0usize;
        while let Some(unit) = next_display_width_unit(&span.text, index) {
            index = unit.end;
            let unit_width = unit.width;
            if row_width.saturating_add(unit_width) > max_width
                && (!row.is_empty() || !segment.is_empty())
            {
                if !segment.is_empty() {
                    row.push(AssistantVisualSpan::styled(
                        std::mem::take(&mut segment),
                        style,
                        role,
                    ));
                }
                rows.push(std::mem::take(&mut row));
                row_width = 0;
            }
            segment.push_str(unit.text);
            row_width = row_width.saturating_add(unit_width);
            if row_width >= max_width {
                row.push(AssistantVisualSpan::styled(
                    std::mem::take(&mut segment),
                    style,
                    role,
                ));
                rows.push(std::mem::take(&mut row));
                row_width = 0;
            }
        }
        if !segment.is_empty() {
            row.push(AssistantVisualSpan::styled(segment, style, role));
        }
    }

    if rows.is_empty() || !row.is_empty() {
        rows.push(row);
    }
    rows
}