magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::*;
#[derive(Debug, Clone)]
pub(crate) struct CachedTranscriptCard {
    pub(crate) content_marker: u64,
    pub(crate) card: Arc<crate::tui::transcript_cards::TranscriptCard>,
}

#[derive(Debug)]
pub(crate) struct CachedTranscriptVisualBlock {
    pub(crate) content_marker: u64,
    pub(crate) theme_revision: u64,
    pub(crate) active: bool,
    pub(crate) prepend_blank: bool,
    pub(crate) subagent_card_rows: usize,
    pub(crate) lines: Vec<crate::tui::transcript_cards::TranscriptVisualLine>,
    pub(crate) line_rows: Vec<usize>,
    pub(crate) rows: usize,
    // Streaming blocks keep only their chrome plus a bounded sparse body index.
    pub(crate) streaming: Option<crate::tui::transcript_cards::StreamingBody>,
}

#[derive(Debug, Clone)]
pub(crate) struct CachedTranscriptVisualLines {
    pub(crate) generation: u64,
    pub(crate) theme_revision: u64,
    pub(crate) active: bool,
    pub(crate) lines: Vec<crate::tui::transcript_cards::TranscriptVisualLine>,
    pub(crate) rows: usize,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct CachedTranscriptScrollbarRows {
    pub(crate) rows: usize,
    pub(crate) exact: bool,
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct TranscriptScrollAnchor {
    pub(crate) entry_index: usize,
    pub(crate) row: usize,
    pub(crate) scrollback_rows: usize,
}

#[derive(Debug, Default)]
pub(crate) struct CachedTranscriptHistoryIndex {
    pub(crate) generation: u64,
    pub(crate) source_start: Option<usize>,
    pub(crate) source_len: usize,
    pub(crate) theme_revision: u64,
    pub(crate) active: bool,
    pub(crate) subagent_card_rows: usize,
    pub(crate) next_entry: usize,
    // Newest first; cumulative heights allow binary searches from the bottom.
    pub(crate) blocks: Vec<Arc<CachedTranscriptVisualBlock>>,
    pub(crate) entry_indices: Vec<usize>,
    pub(crate) cumulative_rows: Vec<usize>,
}

// Sparse cursor storage per retained width, independent of response size.
pub(crate) const MAX_STREAMING_CACHE_BYTES: usize = 256 * 1024;

pub(super) const MAX_TRANSCRIPT_WIDTH_CACHES: usize = 4;

#[derive(Debug, Default)]
pub(crate) struct TranscriptRenderCache {
    pub(crate) visual_rows: HashMap<u16, usize>,
    pub(crate) scrollbar_rows: HashMap<u16, CachedTranscriptScrollbarRows>,
    pub(crate) cards: BTreeMap<usize, CachedTranscriptCard>,
    pub(crate) visual_lines: HashMap<u16, CachedTranscriptVisualLines>,
    pub(crate) visual_blocks: HashMap<u16, BTreeMap<usize, Arc<CachedTranscriptVisualBlock>>>,
    // One active body handoff across revision eviction, shared with the live
    // block until then. Never retain a chain of historical response copies.
    pub(crate) streaming_reuse: Option<(u16, usize, Arc<CachedTranscriptVisualBlock>)>,
    pub(crate) history_indices: HashMap<u16, CachedTranscriptHistoryIndex>,
    visual_widths: VecDeque<u16>,
}

impl TranscriptRenderCache {
    pub(crate) fn clear(&mut self) {
        self.clear_visual_projection();
        self.cards.clear();
    }

    pub(crate) fn clear_visual_projection(&mut self) {
        self.clear_visual_aggregates();
        self.visual_blocks.clear();
        self.streaming_reuse = None;
        self.visual_widths.clear();
    }

    pub(crate) fn clear_visual_aggregates(&mut self) {
        self.visual_rows.clear();
        self.scrollbar_rows.clear();
        self.visual_lines.clear();
        self.history_indices.clear();
    }

    pub(crate) fn touch_visual_width(&mut self, width: u16) {
        let width = width.max(1);
        if let Some(index) = self
            .visual_widths
            .iter()
            .position(|cached| *cached == width)
        {
            let width = self.visual_widths.remove(index).expect("cached width");
            self.visual_widths.push_back(width);
            return;
        }

        if self.visual_widths.len() >= MAX_TRANSCRIPT_WIDTH_CACHES
            && let Some(evicted_width) = self.visual_widths.pop_front()
        {
            self.visual_rows.remove(&evicted_width);
            self.scrollbar_rows.remove(&evicted_width);
            self.visual_lines.remove(&evicted_width);
            self.visual_blocks.remove(&evicted_width);
            self.history_indices.remove(&evicted_width);
            if self
                .streaming_reuse
                .as_ref()
                .is_some_and(|(width, _, _)| *width == evicted_width)
            {
                self.streaming_reuse = None;
            }
        }
        self.visual_widths.push_back(width);
    }

    pub(crate) fn insert_visual_block(
        &mut self,
        width: u16,
        entry_index: usize,
        block: Arc<CachedTranscriptVisualBlock>,
    ) {
        let width = width.max(1);
        self.touch_visual_width(width);
        if block.streaming.is_some() {
            self.streaming_reuse = Some((width, entry_index, Arc::clone(&block)));
        } else if self
            .streaming_reuse
            .as_ref()
            .is_some_and(|(_, index, _)| *index == entry_index)
        {
            self.streaming_reuse = None;
        }
        self.visual_blocks
            .entry(width)
            .or_default()
            .insert(entry_index, block);
    }

    pub(crate) fn evict_card(&mut self, entry_index: usize) {
        self.evict_cards(std::iter::once(entry_index));
    }

    pub(crate) fn evict_cards(&mut self, entry_indices: impl IntoIterator<Item = usize>) {
        let entry_indices = entry_indices.into_iter().collect::<Vec<_>>();
        if entry_indices.is_empty() {
            return;
        }
        for entry_index in entry_indices {
            self.cards.remove(&entry_index);
            for blocks in self.visual_blocks.values_mut() {
                blocks.remove(&entry_index);
            }
        }
        self.clear_visual_aggregates();
    }

    pub(crate) fn evict_front(&mut self, old_base: usize, drained: usize) {
        if self.streaming_reuse.as_ref().is_some_and(|(_, index, _)| {
            (old_base..old_base.saturating_add(drained)).contains(index)
        }) {
            self.streaming_reuse = None;
        }
        self.evict_cards(old_base..old_base.saturating_add(drained));
    }
}