magi-code 0.96.1

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

pub(super) fn append_user_prompt_lines(
    lines: &mut Vec<TranscriptVisualLine>,
    state: &MissionControlState,
    card: &TranscriptCard,
    body_style: Style,
    width: u16,
    timestamp: Option<&str>,
    active: bool,
) {
    let theme = state.theme;
    push_body_blank(lines, card, width, theme);
    let timestamp_style = theme
        .muted(active)
        .bg(card_body_background(card.role, theme))
        .add_modifier(Modifier::DIM);
    let body_lines = if card.body.is_empty() {
        vec![""]
    } else {
        card.body.lines().collect::<Vec<_>>()
    };

    for (line_index, body_line) in body_lines.into_iter().enumerate() {
        let first = line_index == 0;
        let mut spans = Vec::new();
        if first {
            if let Some(timestamp) = timestamp {
                spans.push(UserPromptVisualSpan::styled(
                    timestamp,
                    timestamp_style,
                    false,
                ));
                spans.push(UserPromptVisualSpan::styled(" | ", timestamp_style, false));
            }
            let label = if card.title == "Steering injected" {
                "Steering injected: "
            } else {
                "User: "
            };
            spans.push(UserPromptVisualSpan::styled(label, body_style, true));
        }
        if !body_line.is_empty() {
            spans.push(UserPromptVisualSpan::styled(body_line, body_style, true));
        }

        let padding = user_prompt_horizontal_padding(width);
        let wrap_width = usize::from(width).saturating_sub(2 * padding).max(1);
        let wrapped = wrap_user_prompt_spans_to_width(spans, wrap_width);
        let mut copy_line_seen = false;
        for row in wrapped {
            let row_has_copy = row.iter().any(|span| span.copyable);
            let force_copyable = !first && !row_has_copy;
            let copyable = row_has_copy || force_copyable;
            push_user_prompt_row(
                lines,
                row,
                width,
                body_style,
                force_copyable,
                copy_line_seen,
            );
            if copyable {
                copy_line_seen = true;
            }
        }
    }
    push_card_token_footer(lines, state, card, active, width, theme, None);
}

struct UserPromptVisualSpan {
    pub(super) display_text: String,
    pub(super) copy_text: String,
    pub(super) style: Style,
    pub(super) copyable: bool,
}

impl UserPromptVisualSpan {
    pub(super) fn styled(text: impl Into<String>, style: Style, copyable: bool) -> Self {
        let text = text.into();
        Self {
            copy_text: if copyable {
                text.clone()
            } else {
                String::new()
            },
            display_text: text,
            style,
            copyable,
        }
    }

    fn with_copy_text(
        display_text: impl Into<String>,
        copy_text: impl Into<String>,
        style: Style,
        copyable: bool,
    ) -> Self {
        Self {
            display_text: display_text.into(),
            copy_text: copy_text.into(),
            style,
            copyable,
        }
    }
}

fn push_user_prompt_row(
    lines: &mut Vec<TranscriptVisualLine>,
    row: Vec<UserPromptVisualSpan>,
    width: u16,
    body_style: Style,
    force_copyable: bool,
    copy_continuation: bool,
) {
    let mut spans = Vec::with_capacity(row.len());
    let mut copy_text = String::new();
    let mut copy_byte_boundaries = Vec::new();
    let mut copy_column = None;
    let mut display_width = 0usize;
    for span in row {
        if span.copyable {
            copy_column.get_or_insert(display_width);
            let copy_byte_base = copy_text.len();
            copy_text.push_str(&span.copy_text);
            append_copy_byte_boundaries(
                &mut copy_byte_boundaries,
                &copy_byte_boundaries_for_span(&span.display_text, &span.copy_text),
                copy_byte_base,
            );
        }
        display_width =
            display_width.saturating_add(UnicodeWidthStr::width(span.display_text.as_str()));
        spans.push(Span::styled(span.display_text, span.style));
    }

    let copyable = copy_column.is_some() || force_copyable;
    let left = user_prompt_horizontal_padding(width);
    lines.push(TranscriptVisualLine {
        image_row: None,
        status_indicators: Vec::new(),
        line: padded_body_line(spans, width, body_style, (left, left)),
        copy_text,
        copy_byte_range: None,
        copyable,
        copy_continuation: copyable && copy_continuation,
        visual_copy_column_offset: copy_column
            .map(|column| display_width_to_u16(left.saturating_add(column)))
            .unwrap_or_default(),
        visual_copy_byte_boundaries: copy_byte_boundaries,
        hit_regions: Vec::new(),
    });
}

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

    for span in spans {
        let mut segment = String::new();
        let mut copy_segment = String::new();
        let mut segment_has_positive_width = false;
        let mut trailing_zero_width_after_full_row = false;
        let mut index = 0usize;
        while let Some(unit) = next_display_width_unit(&span.display_text, index) {
            index = unit.end;
            let unit_width = unit.width;
            // Keep trailing zero-width units with the visible unit that filled
            // the previous row instead of treating them as leading pending text.
            if unit_width == 0
                && trailing_zero_width_after_full_row
                && segment.is_empty()
                && row.is_empty()
                && row_width == 0
                && let Some(last_row) = rows.last_mut()
            {
                last_row.push(UserPromptVisualSpan::with_copy_text(
                    unit.text,
                    if span.copyable { unit.text } else { "" },
                    span.style,
                    span.copyable,
                ));
                continue;
            }
            trailing_zero_width_after_full_row = false;
            // Keep an oversized grapheme intact in copied text while fitting a
            // deterministic replacement into the single available display cell.
            if unit_width > max_width {
                let carries_leading_zero_width = !segment.is_empty() && !segment_has_positive_width;
                if segment_has_positive_width && !segment.is_empty() {
                    row.push(UserPromptVisualSpan::with_copy_text(
                        std::mem::take(&mut segment),
                        std::mem::take(&mut copy_segment),
                        span.style,
                        span.copyable,
                    ));
                }
                if !row.is_empty() {
                    rows.push(std::mem::take(&mut row));
                }
                row_width = 0;

                let mut replacement_display = if carries_leading_zero_width {
                    std::mem::take(&mut segment)
                } else {
                    String::new()
                };
                replacement_display.push('�');
                let mut replacement_copy = if carries_leading_zero_width {
                    std::mem::take(&mut copy_segment)
                } else {
                    copy_segment.clear();
                    String::new()
                };
                if span.copyable {
                    replacement_copy.push_str(unit.text);
                } else {
                    replacement_copy.clear();
                }
                row.push(UserPromptVisualSpan::with_copy_text(
                    replacement_display,
                    replacement_copy,
                    span.style,
                    span.copyable,
                ));
                rows.push(std::mem::take(&mut row));
                trailing_zero_width_after_full_row = true;
                segment_has_positive_width = false;
                continue;
            }
            if row_width.saturating_add(unit_width) > max_width
                && (!row.is_empty() || !segment.is_empty())
            {
                // A zero-width segment before this unit belongs to the next
                // visible unit. A segment containing a visible unit already
                // owns its trailing zero-width units and stays on this row.
                if segment_has_positive_width && !segment.is_empty() {
                    row.push(UserPromptVisualSpan::with_copy_text(
                        std::mem::take(&mut segment),
                        std::mem::take(&mut copy_segment),
                        span.style,
                        span.copyable,
                    ));
                }
                rows.push(std::mem::take(&mut row));
                row_width = 0;
            }
            segment.push_str(unit.text);
            if span.copyable {
                copy_segment.push_str(unit.text);
            }
            if unit_width > 0 {
                segment_has_positive_width = true;
            }

            row_width = row_width.saturating_add(unit_width);
            if row_width >= max_width {
                row.push(UserPromptVisualSpan::with_copy_text(
                    std::mem::take(&mut segment),
                    std::mem::take(&mut copy_segment),
                    span.style,
                    span.copyable,
                ));
                rows.push(std::mem::take(&mut row));
                row_width = 0;
                segment_has_positive_width = false;
                trailing_zero_width_after_full_row = true;
            }
        }
        if !segment.is_empty() {
            row.push(UserPromptVisualSpan::with_copy_text(
                segment,
                copy_segment,
                span.style,
                span.copyable,
            ));
        }
    }

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