magi-code 0.96.1

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SystemPromptCard {
    pub(crate) title: String,
    pub(crate) content: String,
    pub(crate) tokens: usize,
}

impl SystemPromptCard {
    pub(crate) fn text(&self) -> ratatui::text::Text<'_> {
        use ratatui::text::Line;
        let mut lines = vec![
            Line::from(self.title.as_str()),
            Line::from(format!("~{} tokens", self.tokens)),
            Line::default(),
        ];
        // Separating newlines belong to the estimate, not the card's padding.
        lines.extend(self.content.trim_matches('\n').split('\n').map(Line::from));
        lines.into()
    }

    pub(crate) fn text_width(card_width: u16) -> u16 {
        card_width.saturating_sub(4).max(1)
    }

    pub(crate) fn visual_rows(&self, card_width: u16) -> usize {
        ratatui::widgets::Paragraph::new(self.text())
            .wrap(ratatui::widgets::Wrap { trim: false })
            .line_count(Self::text_width(card_width))
            .saturating_add(2)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SystemPromptModalState {
    /// Summary when ready, otherwise the loading or error message.
    pub(crate) content: String,
    pub(crate) sections: Vec<SystemPromptCard>,
}

impl SystemPromptModalState {
    pub(crate) fn visual_rows(&self, width: u16) -> usize {
        self.sections.iter().fold(
            crate::tui::transcript::ratatui_wrapped_visual_rows(&self.content, width.max(1)),
            |rows, section| {
                rows.saturating_add(1)
                    .saturating_add(section.visual_rows(width))
            },
        )
    }
}

impl MissionControlState {
    pub(crate) fn open_system_prompt_modal(&mut self, content: String) {
        self.show_system_prompt_sections(SystemPromptModalState {
            content,
            sections: Vec::new(),
        });
    }

    pub(crate) fn show_system_prompt_sections(&mut self, modal: SystemPromptModalState) {
        self.modals.system_prompt_modal = Some(modal);
        self.set_scroll_offset(&self.scroll_views.system_prompt, 0);
        self.hide_autocomplete();
    }

    pub(crate) fn close_system_prompt_modal(&mut self) -> bool {
        self.modals.system_prompt_modal.take().is_some()
    }

    pub(crate) fn system_prompt_modal_visible(&self) -> bool {
        self.modals.system_prompt_modal.is_some()
    }

    pub(crate) fn scroll_system_prompt_up_by(
        &mut self,
        rows: u16,
        visible_rows: u16,
        wrap_width: u16,
    ) -> bool {
        let before = self.scroll_offset(&self.scroll_views.system_prompt);
        let overflow = self.system_prompt_scroll_overflow(visible_rows, wrap_width);
        self.set_scroll_offset(
            &self.scroll_views.system_prompt,
            before.saturating_sub(usize::from(rows)).min(overflow),
        );
        self.scroll_offset(&self.scroll_views.system_prompt) != before
    }

    pub(crate) fn scroll_system_prompt_down_by(
        &mut self,
        rows: u16,
        visible_rows: u16,
        wrap_width: u16,
    ) -> bool {
        let before = self.scroll_offset(&self.scroll_views.system_prompt);
        let overflow = self.system_prompt_scroll_overflow(visible_rows, wrap_width);
        self.set_scroll_offset(
            &self.scroll_views.system_prompt,
            before.saturating_add(usize::from(rows)).min(overflow),
        );
        self.scroll_offset(&self.scroll_views.system_prompt) != before
    }

    pub(crate) fn system_prompt_scroll_overflow(
        &self,
        visible_rows: u16,
        wrap_width: u16,
    ) -> usize {
        let Some(modal) = &self.modals.system_prompt_modal else {
            return 0;
        };
        modal
            .visual_rows(wrap_width)
            .min(usize::from(u16::MAX))
            .saturating_sub(visible_rows.max(1) as usize)
    }
}