codex-mobile-bridge 0.3.3

Remote bridge and service manager for codex-mobile.
Documentation
use serde_json::Value;

use super::super::helpers::optional_string;
use super::semantic::normalize_token;

pub(super) fn change_path(change: &Value) -> Option<String> {
    optional_string(change, "path").or_else(|| optional_string(change, "filePath"))
}

pub(super) fn summarize_change_counts(change: &Value) -> (i64, i64) {
    let kind = optional_string(change, "kind")
        .or_else(|| optional_string(change, "type"))
        .or_else(|| optional_string(change, "status"))
        .unwrap_or_default();
    let diff = optional_string(change, "diff")
        .or_else(|| optional_string(change, "content"))
        .unwrap_or_default();
    if looks_like_patch_text(&diff) {
        let (_, _, add_lines, remove_lines) = summarize_diff_text(&diff);
        return (add_lines, remove_lines);
    }
    match normalize_token(&kind).as_str() {
        "add" | "added" => (count_content_lines(&diff), 0),
        "delete" | "deleted" => (0, count_content_lines(&diff)),
        _ => (0, 0),
    }
}

pub(super) fn looks_like_patch_text(text: &str) -> bool {
    let trimmed = text.trim();
    trimmed.contains("*** Begin Patch")
        || trimmed.contains("diff --git ")
        || trimmed.contains("@@")
        || trimmed.contains("+++ ")
        || trimmed.contains("--- ")
}

pub(super) fn summarize_diff_text(diff: &str) -> (Option<String>, i64, i64, i64) {
    if diff.contains("*** Begin Patch") {
        return summarize_apply_patch_text(diff);
    }
    summarize_unified_diff_text(diff)
}

fn summarize_apply_patch_text(diff: &str) -> (Option<String>, i64, i64, i64) {
    let mut primary_path = None;
    let mut file_count = 0_i64;
    let mut add_lines = 0_i64;
    let mut remove_lines = 0_i64;
    for line in diff.lines() {
        if let Some(path) = line
            .strip_prefix("*** Update File: ")
            .or_else(|| line.strip_prefix("*** Add File: "))
            .or_else(|| line.strip_prefix("*** Delete File: "))
        {
            file_count += 1;
            if primary_path.is_none() {
                primary_path = Some(path.trim().to_string());
            }
            continue;
        }
        if line.starts_with("+++")
            || line.starts_with("---")
            || line.starts_with("***")
            || line.starts_with("@@")
        {
            continue;
        }
        if line.starts_with('+') {
            add_lines += 1;
        } else if line.starts_with('-') {
            remove_lines += 1;
        }
    }
    (primary_path, file_count.max(1), add_lines, remove_lines)
}

fn summarize_unified_diff_text(diff: &str) -> (Option<String>, i64, i64, i64) {
    let mut primary_path = None;
    let mut file_count = 0_i64;
    let mut add_lines = 0_i64;
    let mut remove_lines = 0_i64;
    for line in diff.lines() {
        if let Some(path) = line
            .strip_prefix("+++ b/")
            .or_else(|| line.strip_prefix("+++ "))
        {
            if path != "/dev/null" {
                file_count += 1;
                if primary_path.is_none() {
                    primary_path = Some(path.trim().to_string());
                }
            }
            continue;
        }
        if line.starts_with("diff --git ") {
            if primary_path.is_none() {
                primary_path = line
                    .split_whitespace()
                    .nth(2)
                    .map(|path| path.trim_start_matches("a/").to_string());
            }
            continue;
        }
        if line.starts_with("+++") || line.starts_with("---") {
            continue;
        }
        if line.starts_with('+') {
            add_lines += 1;
        } else if line.starts_with('-') {
            remove_lines += 1;
        }
    }
    (primary_path, file_count.max(1), add_lines, remove_lines)
}

fn count_content_lines(text: &str) -> i64 {
    text.lines().count() as i64
}