magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::super::*;
use super::{MissionControlApp, completion_worker::CompletionWorker};
use crate::config::{HeaderPanel, PanelLayoutSettings, set_panel_layout};
use crate::tui::state::ModalKind;

pub(super) fn panel_layout(ui: &state::MissionControlState) -> PanelLayoutSettings {
    PanelLayoutSettings {
        open: ui
            .modals
            .header_modals
            .iter()
            .filter_map(|modal| match modal {
                ModalKind::Summary => Some(HeaderPanel::Summary),
                ModalKind::ActivityTree => Some(HeaderPanel::ActivityTree),
                ModalKind::SessionFiles => Some(HeaderPanel::SessionFiles),
                _ => None,
            })
            .collect(),
        pinned: ui.modals.header_modals_pinned,
        opened_by_pin: ui.modals.header_modals_opened_by_pin,
        ..Default::default()
    }
}

pub(super) fn restore_panel_layout(
    ui: &mut state::MissionControlState,
    layout: &PanelLayoutSettings,
) {
    if ui.transcript_only_layout {
        return;
    }
    ui.modals.header_modals.clear();
    for panel in &layout.open {
        let modal = match panel {
            HeaderPanel::Summary => ModalKind::Summary,
            HeaderPanel::ActivityTree => ModalKind::ActivityTree,
            HeaderPanel::SessionFiles => ModalKind::SessionFiles,
        };
        if !ui.modals.header_modals.contains(&modal) {
            ui.modals.header_modals.push(modal);
        }
    }
    ui.modals.header_modals_pinned = layout.pinned && !ui.modals.header_modals.is_empty();
    ui.modals.header_modals_opened_by_pin = layout.opened_by_pin && ui.modals.header_modals_pinned;
    ui.focus_prompt();
}

/// One writer and one replaceable pending layout. No input waits for storage.
#[derive(Default)]
pub(super) struct PanelLayoutPersistenceRuntime {
    worker: Option<CompletionWorker<()>>,
    queued: Option<PanelLayoutSettings>,
    retained_failure: Option<String>,
}

impl PanelLayoutPersistenceRuntime {
    pub(super) fn is_pending(&self) -> bool {
        self.worker.is_some() || self.queued.is_some()
    }

    fn record_result(&mut self, result: Result<(), String>) {
        self.retained_failure = result
            .err()
            .map(|error| format!("failed to save panel layout: {error}"));
    }

    pub(super) fn cleanup(&mut self, paths: &crate::config::McPaths) -> Option<String> {
        // Terminal has been restored. Finish the older write before saving the
        // latest queued layout, even when its wake could not be delivered.
        if let Some(worker) = self.worker.take() {
            self.record_result(worker.join().map(|_| ()));
        }
        if let Some(layout) = self.queued.take() {
            self.record_result(set_panel_layout(paths, &layout).map_err(|error| error.to_string()));
        }
        self.retained_failure.take()
    }
}

impl MissionControlApp {
    pub(super) fn persist_panel_layout_if_changed(
        &mut self,
        before: &PanelLayoutSettings,
        ui: &mut state::MissionControlState,
    ) {
        if ui.transcript_only_layout {
            return;
        }
        let layout = panel_layout(ui);
        if &layout == before {
            return;
        }
        self.settings.tui.panel_layout = layout.clone();
        self.panel_layout_persistence.queued = Some(layout);
        self.reap_panel_layout_persistence(ui);
    }

    pub(super) fn reap_panel_layout_persistence(
        &mut self,
        ui: &mut state::MissionControlState,
    ) -> bool {
        let runtime = &mut self.panel_layout_persistence;
        if !runtime.is_pending() {
            return false;
        }
        if let Some(worker) = &runtime.worker {
            if !worker.is_finished() {
                return false;
            }
            let worker = runtime.worker.take().expect("finished panel layout worker");
            runtime.record_result(worker.join().map(|_| ()));
        }
        if let Some(layout) = runtime.queued.take() {
            let paths = self.config.paths.clone();
            match CompletionWorker::spawn("panel-layout-save", self.events.clone(), move || {
                set_panel_layout(&paths, &layout).map_err(|error| error.to_string())
            }) {
                Ok(worker) => runtime.worker = Some(worker),
                Err(error) => runtime.record_result(Err(error.to_string())),
            }
        }
        if let Some(error) = &runtime.retained_failure
            && ui.status != *error
        {
            ui.status = error.clone();
            return true;
        }
        false
    }
}