fission-command-ui 0.13.0

Terminal UI application for the fission command
Documentation
use super::commands::{execute_ui_command, UiCommand};
use super::routes::UiRoute;
use super::state::{UiDialog, UiState};
use fission::prelude::*;
use fission_command_core::Target;

#[fission_reducer(Navigate)]
pub fn navigate(state: &mut UiState, route: UiRoute) {
    state.route = route;
}

#[fission_reducer(ToggleTheme)]
pub fn toggle_theme(state: &mut UiState) {
    state.theme_mode = state.theme_mode.toggle();
}

#[fission_reducer(ToggleCompactMode)]
pub fn toggle_compact_mode(state: &mut UiState) {
    state.compact_mode = !state.compact_mode;
}

#[fission_reducer(SelectTarget)]
pub fn select_target(state: &mut UiState, target: Target) {
    state.selected_target = Some(target);
    state.selected_device = state
        .devices
        .iter()
        .find(|device| device.target == target && device.available)
        .map(|device| device.id.clone());
}

#[fission_reducer(SelectDevice)]
pub fn select_device(state: &mut UiState, id: String) {
    state.selected_device = Some(id);
}

#[fission_reducer(SelectCommandSession)]
pub fn select_command_session(state: &mut UiState, id: u64) {
    state.select_command_session(id);
}

#[fission_reducer(SetInitName)]
pub fn set_init_name(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    if let Some(change) = ctx.input.text_change() {
        state.init_name = change.new_text.clone();
    }
}

#[fission_reducer(SetInitAppId)]
pub fn set_init_app_id(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    if let Some(change) = ctx.input.text_change() {
        state.init_app_id = change.new_text.clone();
    }
}

#[fission_reducer(SetInitLocalPath)]
pub fn set_init_local_path(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    if let Some(change) = ctx.input.text_change() {
        state.init_local_path = change.new_text.clone();
    }
}

#[fission_reducer(SetHost)]
pub fn set_host(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    if let Some(change) = ctx.input.text_change() {
        state.host = change.new_text.clone();
    }
}

#[fission_reducer(SetPort)]
pub fn set_port(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    if let Some(change) = ctx.input.text_change() {
        state.port = change.new_text.clone();
    }
}

#[fission_reducer(SetScrollbackLimitInput)]
pub fn set_scrollback_limit_input(state: &mut UiState, ctx: &mut ReducerContext<UiState>) {
    let Some(change) = ctx.input.text_change() else {
        return;
    };
    let value = &change.new_text;
    state.scrollback_limit_input = value.clone();
    if let Some(limit) = parse_scrollback_limit(value) {
        state.set_scrollback_limit(limit);
    }
}

#[fission_reducer(SetScrollbackLimit)]
pub fn set_scrollback_limit(state: &mut UiState, limit: usize) {
    state.set_scrollback_limit(limit);
}

#[fission_reducer(ToggleStrict)]
pub fn toggle_strict(state: &mut UiState) {
    state.strict = !state.strict;
}

#[fission_reducer(ToggleRelease)]
pub fn toggle_release(state: &mut UiState) {
    state.release = !state.release;
}

#[fission_reducer(ToggleDetach)]
pub fn toggle_detach(state: &mut UiState) {
    state.detach = !state.detach;
}

#[fission_reducer(ToggleNoOpen)]
pub fn toggle_no_open(state: &mut UiState) {
    state.no_open = !state.no_open;
}

#[fission_reducer(ToggleHeadless)]
pub fn toggle_headless(state: &mut UiState) {
    state.headless = !state.headless;
}

#[fission_reducer(ExecuteCommand)]
pub fn execute_command(state: &mut UiState, command: UiCommand) {
    execute_ui_command(state, command);
}

#[fission_reducer(RequestCommand)]
pub fn request_command(state: &mut UiState, command: UiCommand) {
    state.request_command_confirmation(command);
}

#[fission_reducer(ConfirmDialog)]
pub fn confirm_dialog(state: &mut UiState) {
    let Some(dialog) = state.pending_dialog.take() else {
        return;
    };
    match dialog {
        UiDialog::Command { command, .. } => execute_ui_command(state, command),
        UiDialog::Exit { .. } => {
            state.exit_confirmed = true;
        }
    }
}

#[fission_reducer(CancelDialog)]
pub fn cancel_dialog(state: &mut UiState) {
    state.pending_dialog = None;
}

fn parse_scrollback_limit(value: &str) -> Option<usize> {
    let compact = value.trim().replace('_', "");
    if compact.is_empty() {
        return None;
    }
    let lower = compact.to_ascii_lowercase();
    let (digits, multiplier) = if let Some(digits) = lower.strip_suffix('k') {
        (digits, 1_000usize)
    } else if let Some(digits) = lower.strip_suffix('m') {
        (digits, 1_000_000usize)
    } else {
        (lower.as_str(), 1usize)
    };
    digits
        .parse::<usize>()
        .ok()
        .and_then(|value| value.checked_mul(multiplier))
        .filter(|value| *value > 0)
}