magi-code 0.82.1

Repository-aware CLI coding agent for terminal work
Documentation
//! Plain streaming bodies keep sparse cursors, never a vector of formatted history.
//! Revalidate the fully sanitized completed prefix before reusing any cursor.
use super::*;

pub(super) fn is_windowed(card: &TranscriptCard) -> bool {
    card.role == TranscriptCardRole::Assistant
        && card.status == TranscriptCardStatus::Running
        && card.id.entry_index != usize::MAX
        && card.body_lines.is_empty()
        && card.metadata.is_empty()
        && card.children.is_empty()
}

#[derive(Debug)]
pub(crate) struct StreamingBody {
    card: Arc<TranscriptCard>,
    checkpoints: Vec<Checkpoint>,
    checkpoint_stride: usize,
    pub(super) rows: usize,
    copy_bytes: usize,
    width: u16,
    theme: MissionControlTheme,
    body_style: Style,
}

#[derive(Debug, Clone, Copy)]
struct Checkpoint {
    row: usize,
    cursor: Cursor,
}

#[derive(Debug, Clone, Copy)]
struct Cursor {
    byte: usize,
    line_end: usize,
    indent: usize,
    continuation: bool,
    copy_byte: usize,
    finished: bool,
}

struct PlainRow {
    source: Range<usize>,
    indent: usize,
    continuation: bool,
    copy: Range<usize>,
}

impl Cursor {
    fn new(body: &str) -> Self {
        Self {
            byte: 0,
            line_end: body.find('\n').unwrap_or(body.len()),
            indent: 2,
            continuation: false,
            copy_byte: 0,
            finished: false,
        }
    }

    fn next_line(&mut self, body: &str) {
        if self.line_end == body.len() {
            self.finished = true;
        } else {
            self.byte = self.line_end + 1;
            self.line_end = body[self.byte..]
                .find('\n')
                .map_or(body.len(), |end| self.byte + end);
            self.indent = 2;
            self.continuation = false;
            self.copy_byte += 1;
        }
    }

    // Same span boundary, grapheme, control filtering and hard-wrap rules as
    // wrap_assistant_spans_to_width. Generated lines may occupy multiple terminal rows.
    fn next(&mut self, body: &str, max_width: usize) -> Option<PlainRow> {
        while !self.finished {
            let mut row = PlainRow {
                source: self.byte..self.byte,
                indent: 0,
                continuation: self.continuation,
                copy: self.copy_byte..self.copy_byte,
            };
            let mut row_width = 0usize;
            let mut has_units = false;
            while self.indent > 0 {
                self.indent -= 1;
                row.indent += 1;
                self.copy_byte += 1;
                row_width += 1;
                has_units = true;
                if row_width >= max_width {
                    row.copy.end = self.copy_byte;
                    self.continuation = true;
                    return Some(row);
                }
            }
            while let Some(unit) = next_display_width_unit(&body[..self.line_end], self.byte) {
                if has_units && row_width.saturating_add(unit.width) > max_width {
                    row.copy.end = self.copy_byte;
                    self.continuation = true;
                    return Some(row);
                }
                self.byte = unit.end;
                row.source.end = self.byte;
                self.copy_byte += unit.text.len();
                row_width = row_width.saturating_add(unit.width);
                has_units = true;
                if row_width >= max_width {
                    row.copy.end = self.copy_byte;
                    self.continuation = true;
                    return Some(row);
                }
            }
            row.copy.end = self.copy_byte;
            self.next_line(body);
            if has_units {
                return Some(row);
            }
        }
        None
    }
}

impl StreamingBody {
    pub(super) fn new(
        card: Arc<TranscriptCard>,
        width: u16,
        theme: MissionControlTheme,
        active: bool,
        previous: Option<&Self>,
    ) -> Self {
        // At most two indent lines per hard line plus one line per source byte.
        // Sample generated lines, not measured terminal rows: Unicode can make
        // a generated line taller, but must not increase the cursor budget.
        let max_checkpoints = transcript::MAX_STREAMING_CACHE_BYTES / size_of::<Checkpoint>();
        let max_lines = card.body.len().saturating_mul(3).saturating_add(2);
        let stride = max_lines.div_ceil(max_checkpoints).max(1);
        // A newline is a grapheme boundary. Never trust the unfinished hard
        // line: appends can join its last grapheme or retroactively redact text.
        let previous = previous.and_then(|old| {
            // Avoid another source scan for an unbroken response.
            if old.checkpoints.first()?.cursor.line_end == old.card.body.len() {
                return None;
            }
            let prefix_end = old.card.body.rfind('\n')? + 1;
            (old.width == width
                && card.body.get(..prefix_end) == Some(&old.card.body[..prefix_end]))
            .then_some((old, prefix_end))
        });
        // Keep sampling aligned with the old index when growing the stride.
        // This only thins checkpoints; it never increases their byte budget.
        let stride = previous.map_or(stride, |(old, _)| {
            old.checkpoint_stride * stride.div_ceil(old.checkpoint_stride)
        });
        let body_style =
            role_body_style(card.role, active, theme).bg(card_body_background(card.role, theme));
        let mut body = Self {
            card,
            checkpoints: Vec::with_capacity(32.min(max_lines).min(max_checkpoints)),
            checkpoint_stride: stride,
            rows: 0,
            copy_bytes: 0,
            width,
            theme,
            body_style,
        };
        let mut cursor = Cursor::new(&body.card.body);
        let mut generated_lines = 0usize;
        if let Some((old, prefix_end)) = previous {
            let reusable = old
                .checkpoints
                .partition_point(|point| point.cursor.line_end < prefix_end);
            if reusable > 0 {
                let thinning = stride / old.checkpoint_stride;
                let resume_index = (reusable - 1) / thinning * thinning;
                let retained = resume_index / thinning;
                if retained > body.checkpoints.capacity() {
                    body.checkpoints.reserve_exact(retained);
                }
                body.checkpoints.extend(
                    old.checkpoints[..resume_index]
                        .iter()
                        .step_by(thinning)
                        .copied(),
                );
                let resume = old.checkpoints[resume_index];
                cursor = resume.cursor;
                body.rows = resume.row;
                body.copy_bytes = cursor.copy_byte;
                generated_lines = resume_index * old.checkpoint_stride;
            }
        }
        loop {
            let start = cursor;
            let Some(row) = cursor.next(&body.card.body, body_wrap_width(width)) else {
                break;
            };
            if generated_lines.is_multiple_of(stride) {
                // Grow for actual rows, not the conservative byte-based upper
                // bound; avoid paying the entire cache budget for short replies.
                let checkpoints = &mut body.checkpoints;
                if checkpoints.len() == checkpoints.capacity() {
                    let capacity = checkpoints.capacity();
                    checkpoints.reserve_exact((capacity * 2).min(max_checkpoints) - capacity);
                }
                checkpoints.push(Checkpoint {
                    row: body.rows,
                    cursor: start,
                });
            }
            body.copy_bytes = row.copy.end;
            body.rows += body.measured_line(&row).map_or(1, |(_, rows)| rows);
            generated_lines += 1;
        }
        debug_assert!(
            body.checkpoints.capacity() * size_of::<Checkpoint>()
                <= transcript::MAX_STREAMING_CACHE_BYTES
        );
        body
    }

    // ASCII lines fit by construction. For everything else, measure the exact
    // padded/styled line: Ratatui's CellWidth and whole-span padding widths can
    // disagree with our per-grapheme wrapping (e.g. halfwidth sound marks and
    // Arabic ligatures). No Unicode allowlist or formatted history is retained.
    fn measured_line(&self, row: &PlainRow) -> Option<(TranscriptVisualLine, usize)> {
        if self.card.body[row.source.clone()].is_ascii() {
            return None;
        }
        let line = self.visual_line(row);
        let rows = visual_line_rows(&line, self.width);
        Some((line, rows))
    }

    fn visual_line(&self, row: &PlainRow) -> TranscriptVisualLine {
        let mut spans = Vec::with_capacity(2);
        if row.indent > 0 {
            spans.push(Span::styled(
                " ".repeat(row.indent),
                self.theme
                    .pane()
                    .bg(card_body_background(self.card.role, self.theme)),
            ));
        }
        let text: String = self.card.body[row.source.clone()]
            .graphemes(true)
            .filter(|unit| !unit.chars().any(char::is_control))
            .collect();
        let copy_text = format!("{}{text}", " ".repeat(row.indent));
        if !text.is_empty() {
            spans.push(Span::styled(
                text,
                self.theme
                    .display_role(DisplayRole::Plain)
                    .bg(card_body_background(self.card.role, self.theme)),
            ));
        }
        TranscriptVisualLine {
            image_row: None,
            status_indicators: Vec::new(),
            line: wide_body_line(spans, self.width, self.body_style),
            copy_text,
            copy_byte_range: None,
            copyable: true,
            hit_regions: Vec::new(),
            copy_continuation: row.continuation,
            visual_copy_column_offset: body_line_copy_offset(self.width, 0),
            visual_copy_byte_boundaries: Vec::new(),
        }
    }

    fn visit<T>(
        &self,
        range: Range<usize>,
        mut visit: impl FnMut(&TranscriptVisualLine, usize, usize, Range<usize>) -> Option<T>,
    ) -> Option<T> {
        if range.start >= range.end || range.start >= self.rows {
            return None;
        }
        let checkpoint = &self.checkpoints[self
            .checkpoints
            .partition_point(|point| point.row <= range.start)
            - 1];
        let mut cursor = checkpoint.cursor;
        let mut row_index = checkpoint.row;
        while row_index < range.end.min(self.rows) {
            let row = cursor.next(&self.card.body, body_wrap_width(self.width))?;
            let measured = self.measured_line(&row);
            let rows = measured.as_ref().map_or(1, |(_, rows)| *rows);
            let next_row = row_index + rows;
            if next_row > range.start {
                let line = measured.map_or_else(|| self.visual_line(&row), |(line, _)| line);
                if let Some(result) = visit(&line, row_index, rows, row.copy) {
                    return Some(result);
                }
            }
            row_index = next_row;
        }
        None
    }
}

impl transcript::CachedTranscriptVisualBlock {
    pub(super) fn copy_bytes(&self) -> usize {
        let mut bytes = 0;
        let mut seen = false;
        for line in &self.lines {
            advance_copy_byte_range(line, &mut bytes, &mut seen);
        }
        bytes
            + self
                .streaming
                .as_ref()
                .map_or(0, |body| 1 + body.copy_bytes)
    }

    // `row` is block-local; byte coordinates describe the complete block copy
    // projection, not just the visible slice. Nonstreaming blocks stay unchanged.
    pub(super) fn visit_lines<T>(
        &self,
        range: Range<usize>,
        mut visit: impl FnMut(&TranscriptVisualLine, usize, usize, Option<(usize, usize)>) -> Option<T>,
    ) -> Option<T> {
        if range.start >= range.end || range.start >= self.rows {
            return None;
        }
        let mut current_row = 0usize;
        let mut bytes = 0;
        let mut seen = false;
        for (index, (line, &rows)) in self.lines.iter().zip(&self.line_rows).enumerate() {
            if index + 1 == self.lines.len()
                && let Some(body) = &self.streaming
            {
                let byte_base = bytes + usize::from(seen);
                let body_range =
                    range.start.saturating_sub(current_row)..range.end.saturating_sub(current_row);
                if let Some(result) = body.visit(body_range, |line, row, rows, copy| {
                    visit(
                        line,
                        current_row + row,
                        rows,
                        Some((byte_base + copy.start, byte_base + copy.end)),
                    )
                }) {
                    return Some(result);
                }
                current_row += body.rows;
                bytes = byte_base + body.copy_bytes;
                seen = true;
            }
            if current_row >= range.end {
                break;
            }
            let copy = advance_copy_byte_range(line, &mut bytes, &mut seen);
            if current_row.saturating_add(rows) > range.start
                && let Some(result) = visit(line, current_row, rows, copy)
            {
                return Some(result);
            }
            current_row += rows;
        }
        None
    }
}