magi-code 0.63.4

Repository-aware CLI coding agent for terminal work
Documentation
use super::super::render_scroll_view_persistent_state;
use super::{SESSIONS_MODAL, block_inner, draw_modal_panel, modal_text_area};
use crate::tui::{state::MissionControlState, theme::MATRIX_GREEN};
use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect, Size},
    text::{Line, Span},
    widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
};
use tui_scrollview::{ScrollView, ScrollbarVisibility};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct SessionModalAreas {
    pub(crate) help: Rect,
    pub(crate) list: Rect,
    pub(crate) preview: Option<Rect>,
}

pub(crate) fn sessions_modal_areas(area: Rect) -> Option<SessionModalAreas> {
    let text_area = modal_text_area(area, "Sessions • Preview", SESSIONS_MODAL)?;
    if text_area.height < 2 {
        return None;
    }
    let help = Rect::new(text_area.x, text_area.y, text_area.width, 1);
    let content = Rect::new(
        text_area.x,
        text_area.y.saturating_add(1),
        text_area.width,
        text_area.height.saturating_sub(1),
    );
    if content.height == 0 {
        return None;
    }
    if content.width >= 50 && content.height >= 3 {
        let columns = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([
                Constraint::Percentage(35),
                Constraint::Length(1),
                Constraint::Percentage(64),
            ])
            .split(content);
        let list = block_inner(columns[0]);
        let preview = block_inner(columns[2]);
        if list.width > 0 && list.height > 0 && preview.width > 0 && preview.height > 0 {
            return Some(SessionModalAreas {
                help,
                list,
                preview: Some(preview),
            });
        }
    }
    Some(SessionModalAreas {
        help,
        list: block_inner(content),
        preview: None,
    })
}

pub(crate) fn sessions_modal_list_area(area: Rect) -> Option<Rect> {
    sessions_modal_areas(area).map(|areas| areas.list)
}

pub(crate) fn sessions_modal_preview_area(area: Rect) -> Option<Rect> {
    sessions_modal_areas(area).and_then(|areas| areas.preview)
}

pub(in crate::tui::render) fn draw_sessions_modal(
    frame: &mut Frame<'_>,
    area: Rect,
    state: &MissionControlState,
) {
    let Some(picker) = &state.modals.session_picker else {
        return;
    };
    let Some(text_area) = draw_modal_panel(frame, area, "Sessions • Preview", SESSIONS_MODAL)
    else {
        return;
    };
    let Some(areas) = sessions_modal_areas(area) else {
        return;
    };
    frame.render_widget(
        Paragraph::new(super::super::hotkey_line(
            "[Tab] focus • [Up]/[Down] move/scroll • Mouse wheel scroll • [Enter] switch • [Esc] close",
            MATRIX_GREEN.muted(false),
        ))
        .style(MATRIX_GREEN.muted(false)),
        areas.help,
    );
    let content_area = Rect::new(
        text_area.x,
        text_area.y.saturating_add(1),
        text_area.width,
        text_area.height.saturating_sub(1),
    );
    let list_block_area = if areas.preview.is_some() {
        Rect::new(
            areas.list.x.saturating_sub(1),
            areas.list.y.saturating_sub(1),
            areas.list.width.saturating_add(2),
            areas.list.height.saturating_add(2),
        )
    } else {
        content_area
    };
    let list_focused =
        areas.preview.is_none() || picker.focus == crate::tui::state::SessionPickerFocus::List;
    frame.render_widget(
        Block::default()
            .title("Sessions")
            .borders(Borders::ALL)
            .border_style(MATRIX_GREEN.border(list_focused))
            .style(MATRIX_GREEN.pane()),
        list_block_area,
    );
    draw_session_list(frame, areas.list, state);
    if let Some(preview_area) = areas.preview {
        let preview_block_area = Rect::new(
            preview_area.x.saturating_sub(1),
            preview_area.y.saturating_sub(1),
            preview_area.width.saturating_add(2),
            preview_area.height.saturating_add(2),
        );
        let preview_focused = picker.focus == crate::tui::state::SessionPickerFocus::Preview;
        frame.render_widget(
            Block::default()
                .title("Preview")
                .borders(Borders::ALL)
                .border_style(MATRIX_GREEN.border(preview_focused))
                .style(MATRIX_GREEN.pane()),
            preview_block_area,
        );
        if preview_area.height > 0 && preview_area.width > 0 {
            let preview_text = state.session_picker_preview_text();
            let content_width = super::super::scroll_view_content_width(preview_area).max(1);
            let content_height = super::super::usize_to_u16_saturating(
                crate::tui::transcript::ratatui_wrapped_visual_rows(&preview_text, content_width),
            );
            render_scroll_view_persistent_state(
                frame,
                preview_area,
                content_height,
                Paragraph::new(preview_text)
                    .style(MATRIX_GREEN.pane())
                    .wrap(ratatui::widgets::Wrap { trim: false }),
                &state.scroll_views.session_preview,
            );
        }
    }
}
fn draw_session_list(frame: &mut Frame<'_>, area: Rect, state: &MissionControlState) {
    if area.height == 0 || area.width == 0 {
        return;
    }
    let Some(picker) = &state.modals.session_picker else {
        return;
    };
    if picker.rows.is_empty() {
        frame.render_widget(
            Paragraph::new("No saved sessions found").style(MATRIX_GREEN.pane()),
            area,
        );
        return;
    }
    let content_height =
        super::super::usize_to_u16_saturating(picker.rows.len().max(usize::from(area.height)));
    let content_width = super::super::scroll_view_content_width(area);
    let mut view = ScrollView::new(Size::new(content_width, content_height))
        .vertical_scrollbar_visibility(ScrollbarVisibility::Always)
        .horizontal_scrollbar_visibility(ScrollbarVisibility::Never);
    let items = picker
        .rows
        .iter()
        .map(|row| {
            let marker = if row.is_current {
                "● current "
            } else {
                "          "
            };
            ListItem::new(Line::from(vec![Span::raw(format!(
                "{marker}{}",
                row.label
            ))]))
        })
        .collect::<Vec<_>>();
    let mut list_state = ListState::default().with_selected(Some(picker.selected));
    view.render_stateful_widget(
        List::new(items)
            .style(MATRIX_GREEN.pane())
            .highlight_style(MATRIX_GREEN.selection())
            .highlight_symbol("> "),
        Rect::new(0, 0, content_width, content_height),
        &mut list_state,
    );
    frame.render_stateful_widget(
        view,
        area,
        &mut *state.scroll_views.session_list.borrow_mut(),
    );
}