magi-code 0.96.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::super as input;
use super::{KeyCode, KeyEvent, KeyModifiers, MissionControlState, PickerViewport};

pub(super) fn classify_theme_picker_key(
    key: KeyEvent,
    state: &MissionControlState,
    viewport: Option<PickerViewport>,
    query_visible: bool,
) -> input::InputCommand {
    if state.theme_picker_save_pending() {
        return input::InputCommand::None;
    }

    let preview = |id: Option<String>, display: input::DisplayCommand| match id {
        Some(id) => {
            input::InputCommand::action_with_display(input::InputAction::PreviewTheme(id), display)
        }
        None => input::InputCommand::display(display),
    };
    if query_visible && input::keybindings::is_theme_variant_switch_key(key) {
        let Some(picker) = state
            .modals
            .theme_picker
            .as_ref()
            .filter(|picker| !picker.loading)
        else {
            return input::InputCommand::None;
        };
        return preview(
            picker.selected_id_after_filter(&picker.query, picker.next_variant()),
            input::DisplayCommand::Modal(input::ModalCommand::ThemeSwitchVariant),
        );
    }
    let visible_rows = viewport.map(|viewport| viewport.visible_rows as usize);
    match (key.code, key.modifiers) {
        (KeyCode::Up, KeyModifiers::NONE) => {
            let Some(visible_rows) = visible_rows else {
                return input::InputCommand::None;
            };
            preview(
                theme_selected_id_after_move(state, 1, false),
                input::DisplayCommand::Modal(input::ModalCommand::ThemeMove {
                    amount: 1,
                    down: false,
                    visible_rows,
                }),
            )
        }
        (KeyCode::Down, KeyModifiers::NONE) => {
            let Some(visible_rows) = visible_rows else {
                return input::InputCommand::None;
            };
            preview(
                theme_selected_id_after_move(state, 1, true),
                input::DisplayCommand::Modal(input::ModalCommand::ThemeMove {
                    amount: 1,
                    down: true,
                    visible_rows,
                }),
            )
        }
        (KeyCode::PageUp, KeyModifiers::NONE) => {
            let Some(visible_rows) = visible_rows else {
                return input::InputCommand::None;
            };
            preview(
                theme_selected_id_after_move(state, visible_rows.max(1), false),
                input::DisplayCommand::Modal(input::ModalCommand::ThemeMove {
                    amount: visible_rows.max(1),
                    down: false,
                    visible_rows,
                }),
            )
        }
        (KeyCode::PageDown, KeyModifiers::NONE) => {
            let Some(visible_rows) = visible_rows else {
                return input::InputCommand::None;
            };
            preview(
                theme_selected_id_after_move(state, visible_rows.max(1), true),
                input::DisplayCommand::Modal(input::ModalCommand::ThemeMove {
                    amount: visible_rows.max(1),
                    down: true,
                    visible_rows,
                }),
            )
        }
        (KeyCode::Enter, KeyModifiers::NONE)
            if viewport.is_some()
                && state
                    .modals
                    .theme_picker
                    .as_ref()
                    .is_some_and(|picker| !picker.loading)
                && state.theme_picker_selected_id().is_some() =>
        {
            input::InputCommand::action(input::InputAction::ConfirmTheme)
        }
        (KeyCode::Esc, KeyModifiers::NONE) => {
            input::InputCommand::action(input::InputAction::CancelTheme)
        }
        (KeyCode::Backspace, KeyModifiers::NONE) if query_visible => preview(
            theme_selected_id_after_edit(state, ThemeQueryEdit::Backspace),
            input::DisplayCommand::Modal(input::ModalCommand::ThemeBackspace),
        ),
        (KeyCode::Delete, KeyModifiers::NONE) if query_visible => preview(
            theme_selected_id_after_edit(state, ThemeQueryEdit::Delete),
            input::DisplayCommand::Modal(input::ModalCommand::ThemeDelete),
        ),
        (KeyCode::Left, KeyModifiers::NONE) if query_visible => input::InputCommand::display(
            input::DisplayCommand::Modal(input::ModalCommand::ThemeCursorLeft),
        ),
        (KeyCode::Right, KeyModifiers::NONE) if query_visible => input::InputCommand::display(
            input::DisplayCommand::Modal(input::ModalCommand::ThemeCursorRight),
        ),
        (KeyCode::Home, KeyModifiers::NONE) if query_visible => input::InputCommand::display(
            input::DisplayCommand::Modal(input::ModalCommand::ThemeCursorHome),
        ),
        (KeyCode::End, KeyModifiers::NONE) if query_visible => input::InputCommand::display(
            input::DisplayCommand::Modal(input::ModalCommand::ThemeCursorEnd),
        ),
        (KeyCode::Char(ch), modifiers)
            if query_visible && (modifiers.is_empty() || modifiers == KeyModifiers::SHIFT) =>
        {
            preview(
                theme_selected_id_after_edit(state, ThemeQueryEdit::Insert(ch)),
                input::DisplayCommand::Modal(input::ModalCommand::ThemeInsertChar(ch)),
            )
        }
        _ => input::InputCommand::None,
    }
}

#[derive(Debug, Clone, Copy)]
enum ThemeQueryEdit {
    Insert(char),
    Backspace,
    Delete,
}

fn theme_selected_id_after_move(
    state: &MissionControlState,
    amount: usize,
    down: bool,
) -> Option<String> {
    let picker = state.modals.theme_picker.as_ref()?;
    if picker.loading || picker.filtered_indices.is_empty() {
        return None;
    }
    let selected = if down {
        picker
            .selected
            .saturating_add(amount)
            .min(picker.filtered_indices.len() - 1)
    } else {
        picker.selected.saturating_sub(amount)
    };
    picker
        .filtered_indices
        .get(selected)
        .and_then(|index| picker.entries.get(*index))
        .map(|entry| entry.id.clone())
}

fn theme_selected_id_after_edit(
    state: &MissionControlState,
    edit: ThemeQueryEdit,
) -> Option<String> {
    use unicode_segmentation::UnicodeSegmentation;

    let picker = state.modals.theme_picker.as_ref()?;
    if picker.loading {
        return None;
    }
    let mut query = picker.query.clone();
    let cursor = picker.query_cursor;
    match edit {
        ThemeQueryEdit::Insert(ch) => {
            query.insert(cursor, ch);
        }
        ThemeQueryEdit::Backspace => {
            if let Some((previous, _)) = query[..cursor].grapheme_indices(true).next_back() {
                query.drain(previous..cursor);
            }
        }
        ThemeQueryEdit::Delete => {
            if let Some(grapheme) = query[cursor..].graphemes(true).next() {
                query.drain(cursor..cursor + grapheme.len());
            }
        }
    }

    picker.selected_id_after_filter(&query, picker.active_variant)
}