prodex 0.2.132

OpenAI profile pooling and safe auto-rotate for Codex CLI and Claude Code
Documentation
use super::*;

pub(super) fn section_header(title: &str) -> String {
    section_header_with_width(title, current_cli_width())
}

pub(super) fn section_header_with_width(title: &str, total_width: usize) -> String {
    let prefix = format!("[ {title} ] ");
    let width = text_width(&prefix);
    if width >= total_width {
        return prefix;
    }

    format!("{prefix}{}", "=".repeat(total_width - width))
}

pub(super) fn text_width(value: &str) -> usize {
    value.chars().count()
}

pub(super) fn fit_cell(value: &str, width: usize) -> String {
    if width == 0 {
        return String::new();
    }

    if text_width(value) <= width {
        return value.to_string();
    }

    if width <= 3 {
        return ".".repeat(width);
    }

    let mut output = String::new();
    for ch in value.chars().take(width - 3) {
        output.push(ch);
    }
    output.push_str("...");
    output
}

pub(super) fn chunk_token(token: &str, width: usize) -> Vec<String> {
    if width == 0 {
        return vec![String::new()];
    }
    if token.is_empty() {
        return vec![String::new()];
    }

    let mut chunks = Vec::new();
    let mut current = String::new();
    for ch in token.chars() {
        current.push(ch);
        if text_width(&current) >= width {
            chunks.push(std::mem::take(&mut current));
        }
    }

    if !current.is_empty() {
        chunks.push(current);
    }

    chunks
}

pub(super) fn wrap_text(input: &str, width: usize) -> Vec<String> {
    if width == 0 {
        return vec![String::new()];
    }

    let mut lines = Vec::new();
    for paragraph in input.lines() {
        if paragraph.trim().is_empty() {
            lines.push(String::new());
            continue;
        }

        let mut current = String::new();
        for word in paragraph.split_whitespace() {
            for piece in chunk_token(word, width) {
                if current.is_empty() {
                    current.push_str(&piece);
                } else if text_width(&current) + 1 + text_width(&piece) <= width {
                    current.push(' ');
                    current.push_str(&piece);
                } else {
                    lines.push(std::mem::take(&mut current));
                    current.push_str(&piece);
                }
            }
        }

        if !current.is_empty() {
            lines.push(current);
        }
    }

    if lines.is_empty() {
        lines.push(String::new());
    }

    lines
}

pub(super) fn current_cli_width() -> usize {
    terminal_width_chars()
        .unwrap_or(CLI_WIDTH)
        .max(CLI_MIN_WIDTH)
}

pub(super) fn terminal_size_override_usize(env_key: &str) -> Option<usize> {
    env::var(env_key)
        .ok()
        .and_then(|value| value.trim().parse::<usize>().ok())
        .filter(|value| *value > 0)
}

pub(super) fn terminal_dimensions_from_tty() -> Option<(usize, usize)> {
    let tty = fs::File::open("/dev/tty").ok()?;
    let output = Command::new("stty").arg("size").stdin(tty).output().ok()?;
    if !output.status.success() {
        return None;
    }

    let text = String::from_utf8(output.stdout).ok()?;
    let mut parts = text.split_whitespace();
    let rows = parts.next()?.parse::<usize>().ok()?;
    let cols = parts.next()?.parse::<usize>().ok()?;
    Some((rows, cols))
}

pub(super) fn terminal_width_chars() -> Option<usize> {
    terminal_size_override_usize("PRODEX_TERM_COLUMNS")
        .or_else(|| terminal_dimensions_from_tty().map(|(_, cols)| cols))
}

pub(super) fn terminal_height_lines() -> Option<usize> {
    terminal_size_override_usize("PRODEX_TERM_LINES")
        .or_else(|| terminal_size_override_usize("LINES"))
        .or_else(|| terminal_dimensions_from_tty().map(|(rows, _)| rows))
}

pub(super) fn panel_label_width(fields: &[(String, String)], total_width: usize) -> usize {
    let longest = fields
        .iter()
        .map(|(label, _)| text_width(label) + 1)
        .max()
        .unwrap_or(CLI_LABEL_WIDTH);
    let max_by_width = total_width
        .saturating_sub(20)
        .clamp(CLI_MIN_LABEL_WIDTH, CLI_MAX_LABEL_WIDTH);
    let preferred_cap = (total_width / 4).clamp(CLI_MIN_LABEL_WIDTH, CLI_MAX_LABEL_WIDTH);
    longest.clamp(CLI_MIN_LABEL_WIDTH, max_by_width.min(preferred_cap))
}

pub(super) fn format_field_lines_with_layout(
    label: &str,
    value: &str,
    total_width: usize,
    label_width: usize,
) -> Vec<String> {
    let label = format!("{label}:");
    let value_width = total_width.saturating_sub(label_width + 1).max(1);
    let wrapped = wrap_text(value, value_width);
    let mut lines = Vec::new();

    for (index, line) in wrapped.into_iter().enumerate() {
        let field_label = if index == 0 { label.as_str() } else { "" };
        lines.push(format!(
            "{field_label:<label_w$} {line}",
            label_w = label_width
        ));
    }

    lines
}

pub(super) fn print_panel(title: &str, fields: &[(String, String)]) {
    let total_width = current_cli_width();
    let label_width = panel_label_width(fields, total_width);
    println!("{}", section_header_with_width(title, total_width));
    for (label, value) in fields {
        for line in format_field_lines_with_layout(label, value, total_width, label_width) {
            println!("{line}");
        }
    }
}

pub(super) fn render_panel(title: &str, fields: &[(String, String)]) -> String {
    let total_width = current_cli_width();
    let label_width = panel_label_width(fields, total_width);
    let mut lines = vec![section_header_with_width(title, total_width)];
    for (label, value) in fields {
        lines.extend(format_field_lines_with_layout(
            label,
            value,
            total_width,
            label_width,
        ));
    }
    lines.join("\n")
}

pub(super) fn print_wrapped_stderr(message: &str) {
    for line in wrap_text(message, current_cli_width()) {
        eprintln!("{line}");
    }
}