magi-code 0.77.1

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

impl MissionControlApp {
    pub(super) fn open_theme_picker(&mut self, ui_state: &mut state::MissionControlState) {
        if ui_state.theme_picker_visible() {
            return;
        }
        let opening = self.appearance.clone();
        let cached = self.theme_catalog.clone();
        self.next_theme_request_id = self.next_theme_request_id.saturating_add(1);
        let request_id = self.next_theme_request_id;
        self.theme_picker_runtime = Some(ThemePickerRuntime {
            opening_appearance: opening.clone(),
            opening_revision: self.theme_revision,
            catalog: cached.clone(),
            preview_appearance: opening.clone(),
            preview_revision: self.theme_revision,
        });
        let (entries, notice, loading) =
            cached.as_ref().map_or((Vec::new(), None, true), |catalog| {
                (
                    catalog.entries(),
                    (!catalog.diagnostics().is_empty()).then(|| catalog.diagnostics().join("; ")),
                    false,
                )
            });
        ui_state.open_theme_picker(
            opening.theme.id.clone(),
            self.theme_revision,
            loading,
            Some(request_id),
            entries,
            notice,
        );
        let sender = self.events.clone();
        let home = self.config.paths.root.clone();
        let handle = thread::spawn(move || {
            let result = Ok(crate::appearance::ThemeCatalog::load(&home));
            let delivery =
                send_tui_event(&sender, TuiEvent::ThemeCatalogLoaded { request_id, result })
                    .map_err(|e| e.to_string());
            ThemeWorkerResult { delivery }
        });
        self.theme_workers.push(ThemeWorker {
            request_id,
            kind: ThemeWorkerKind::Catalog,
            handle,
        });
    }

    fn install_theme(
        &mut self,
        appearance: crate::appearance::RuntimeAppearance,
        ui_state: &mut state::MissionControlState,
    ) {
        self.theme_revision = self.theme_revision.saturating_add(1);
        self.appearance = appearance.clone();
        let theme = crate::tui::theme::MissionControlTheme::from_runtime_appearance(
            &appearance,
            self.theme_revision,
        );
        ui_state.color_enabled = appearance.policy.color_enabled;
        ui_state.set_theme(theme, appearance.reduced_motion);
        if let Some(runtime) = self.theme_picker_runtime.as_mut() {
            runtime.preview_appearance = appearance;
            runtime.preview_revision = self.theme_revision;
        }
    }

    pub(super) fn preview_theme(&mut self, id: &str, ui_state: &mut state::MissionControlState) {
        let Some(runtime) = self.theme_picker_runtime.as_ref() else {
            return;
        };
        let Some(catalog) = runtime.catalog.as_ref() else {
            return;
        };
        let Some(appearance) = catalog.resolve_runtime_appearance(id, &self.appearance) else {
            return;
        };
        if ui_state.theme_picker_selected_id().as_deref() != Some(id) {
            return;
        }
        self.install_theme(appearance, ui_state);
        if let Some(picker) = ui_state.modals.theme_picker.as_mut() {
            picker.preview_theme_id = id.to_string();
        }
    }

    pub(super) fn cancel_theme(&mut self, ui_state: &mut state::MissionControlState) {
        if self.pending_theme_save.is_some() {
            return;
        }
        if let Some(runtime) = self.theme_picker_runtime.take() {
            self.theme_revision = self.theme_revision.max(runtime.opening_revision);
            self.install_theme(runtime.opening_appearance, ui_state);
        }
        ui_state.close_theme_picker();
    }

    pub(super) fn save_theme(&mut self, ui_state: &mut state::MissionControlState) {
        let Some(id) = ui_state.theme_picker_selected_id() else {
            return;
        };
        if ui_state.theme_picker_preview_id() != Some(id.as_str())
            || self.pending_theme_save.is_some()
            || ui_state.theme_picker_save_pending()
        {
            return;
        }
        let revision = self.theme_revision;
        self.next_theme_request_id = self.next_theme_request_id.saturating_add(1);
        let request_id = self.next_theme_request_id;
        self.pending_theme_save = Some(PendingThemeSave {
            request_id,
            theme_id: id.clone(),
            revision,
        });
        ui_state.set_theme_picker_save_pending();
        let paths = self.config.paths.clone();
        let sender = self.events.clone();
        let worker_id = request_id;
        let handle = thread::spawn(move || {
            let result =
                crate::config::set_appearance_theme(&paths, &id).map_err(|e| e.to_string());
            let delivery = send_tui_event(
                &sender,
                TuiEvent::ThemePersisted {
                    request_id,
                    theme_id: id,
                    revision,
                    result,
                },
            )
            .map_err(|e| e.to_string());
            ThemeWorkerResult { delivery }
        });
        self.theme_workers.push(ThemeWorker {
            request_id: worker_id,
            kind: ThemeWorkerKind::Save,
            handle,
        });
    }

    pub(super) fn handle_theme_catalog(
        &mut self,
        ui_state: &mut state::MissionControlState,
        request_id: u64,
        result: Result<crate::appearance::ThemeCatalog, String>,
    ) -> bool {
        let current = ui_state.theme_picker_catalog_request_id() == Some(request_id);
        match result {
            Ok(catalog) => {
                if request_id >= self.theme_catalog_request_id {
                    self.theme_catalog_request_id = request_id;
                    self.theme_catalog = Some(catalog.clone());
                }
                if current {
                    if let Some(runtime) = self.theme_picker_runtime.as_mut() {
                        runtime.catalog = Some(catalog.clone());
                    }
                    let notice = (!catalog.diagnostics().is_empty())
                        .then(|| catalog.diagnostics().join("; "));
                    return ui_state.set_theme_picker_catalog(catalog.entries(), notice, 10);
                }
                false
            }
            Err(error) if current => ui_state.set_theme_picker_error(error),
            Err(_) => false,
        }
    }

    pub(super) fn handle_theme_persisted(
        &mut self,
        ui_state: &mut state::MissionControlState,
        request_id: u64,
        id: String,
        revision: u64,
        result: Result<(), String>,
    ) -> bool {
        let Some(pending) = self.pending_theme_save.as_ref() else {
            return false;
        };
        if pending.request_id != request_id
            || pending.theme_id != id
            || pending.revision != revision
        {
            return false;
        }
        self.pending_theme_save = None;
        match result {
            Ok(()) => {
                self.theme_cli_override = false;
                ui_state.close_theme_picker();
                self.theme_picker_runtime = None;
                true
            }
            Err(error) => ui_state.set_theme_picker_save_failed(error),
        }
    }

    pub(super) fn reap_theme_workers(&mut self, ui_state: &mut state::MissionControlState) -> bool {
        let mut changed = false;
        let mut active = Vec::with_capacity(self.theme_workers.len());
        for worker in std::mem::take(&mut self.theme_workers) {
            if !worker.handle.is_finished() {
                active.push(worker);
                continue;
            }
            let kind = worker.kind;
            let request_id = worker.request_id;
            match worker.handle.join() {
                Ok(result) => {
                    if let Err(error) = result.delivery {
                        changed = true;
                        if kind == ThemeWorkerKind::Save {
                            self.pending_theme_save = None;
                            ui_state.set_theme_picker_save_failed(format!(
                                "theme save failed: {error}"
                            ));
                        } else if ui_state.theme_picker_visible() {
                            ui_state
                                .set_theme_picker_error(format!("theme catalog failed: {error}"));
                        }
                    }
                }
                Err(_) => {
                    changed = true;
                    if kind == ThemeWorkerKind::Save {
                        self.pending_theme_save = None;
                        ui_state.set_theme_picker_save_failed(format!(
                            "theme save worker {request_id} panicked"
                        ));
                    } else if ui_state.theme_picker_visible() {
                        ui_state.set_theme_picker_error("theme catalog worker panicked".into());
                    }
                }
            }
        }
        self.theme_workers = active;
        changed
    }
    pub(super) fn join_theme_workers_for_cleanup(&mut self) -> Vec<String> {
        let mut errors = Vec::new();
        for worker in std::mem::take(&mut self.theme_workers) {
            match worker.handle.join() {
                Ok(result) => {
                    if let Err(error) = result.delivery {
                        errors.push(format!(
                            "theme {:?} worker {} failed: {error}",
                            worker.kind, worker.request_id
                        ));
                    }
                }
                Err(_) => errors.push(format!(
                    "theme {:?} worker {} panicked",
                    worker.kind, worker.request_id
                )),
            }
        }
        self.pending_theme_save = None;
        errors
    }
}