magi-code 0.96.1

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

pub(super) fn hash_edit_success_content(
    results: &[SectionCommitResult],
    prepared: &[PreparedSection],
) -> String {
    let mut lines = vec![format!("applied {} hash_edit section(s)", results.len())];
    for result in results {
        lines.push(format!("{} {}", result.op, result.header));
    }
    let warnings = prepared
        .iter()
        .flat_map(|entry| entry.warnings.iter())
        .map(format_warning)
        .collect::<Vec<_>>();
    if !warnings.is_empty() {
        lines.push("warnings:".to_string());
        lines.extend(
            warnings
                .into_iter()
                .take(20)
                .map(|warning| format!("- {warning}")),
        );
    }
    let resolutions = prepared
        .iter()
        .flat_map(|entry| entry.block_resolutions.iter())
        .collect::<Vec<_>>();
    if !resolutions.is_empty() {
        lines.push("block resolutions:".to_string());
        lines.extend(resolutions.into_iter().take(20).map(|resolution| {
            format!(
                "- {:?} anchor {} -> {}..{}",
                resolution.op, resolution.anchor_line, resolution.start, resolution.end
            )
        }));
    }
    for entry in prepared {
        if let Some(line) = entry.first_changed_line {
            lines.push(format!(
                "first_changed_line {}: {}",
                entry.source.display(),
                line
            ));
        }
    }
    lines.join("\n")
}
pub(super) fn line_stats(before: &str, after: &str, deleted: bool) -> (usize, usize) {
    if deleted {
        return (0, before.lines().count());
    }
    let diff = similar::TextDiff::from_lines(before, after);
    diff.ops()
        .iter()
        .filter(|op| op.tag() != similar::DiffTag::Equal)
        .fold((0, 0), |(added, removed), op| {
            (added + op.new_range().len(), removed + op.old_range().len())
        })
}

fn format_warning(warning: &ParseWarning) -> String {
    match warning {
        ParseWarning::BareBodyAutoPiped => {
            "bare body rows were treated as payload lines".to_string()
        }
        ParseWarning::StrayDotSkipped { line_num } => {
            format!("stray dot skipped at line {line_num}")
        }
        ParseWarning::ApplyRepair { message } => message.clone(),
    }
}

pub(super) fn append_diff(
    out: &mut String,
    source: &Path,
    dest: Option<&Path>,
    before: &str,
    after: &str,
) -> bool {
    if before == after && dest.is_none() {
        return false;
    }
    if !out.is_empty() {
        out.push('\n');
    }
    let display = dest.unwrap_or(source);
    out.push_str(&format!(
        "diff --git a/{} b/{}\n--- a/{}\n+++ b/{}",
        source.display(),
        display.display(),
        source.display(),
        display.display()
    ));
    if out.len() > HASH_EDIT_OUTPUT_MAX_BYTES {
        return true;
    }
    let diff = similar::TextDiff::from_lines(before, after);
    for op in diff.ops() {
        if op.tag() == similar::DiffTag::Equal {
            continue;
        }
        let old_start = if op.old_range().is_empty() {
            op.old_range().start
        } else {
            op.old_range().start + 1
        };
        let new_start = if op.new_range().is_empty() {
            op.new_range().start
        } else {
            op.new_range().start + 1
        };
        out.push_str(&format!(
            "\n@@ -{},{old_len} +{},{new_len} @@",
            old_start,
            new_start,
            old_len = op.old_range().len(),
            new_len = op.new_range().len(),
        ));
        for change in diff.iter_changes(op) {
            let (prefix, value) = match change.tag() {
                similar::ChangeTag::Delete => ('-', change.value()),
                similar::ChangeTag::Insert => ('+', change.value()),
                similar::ChangeTag::Equal => (' ', change.value()),
            };
            out.push('\n');
            out.push(prefix);
            out.push_str(value.trim_end_matches(['\r', '\n']));
            if out.len() > HASH_EDIT_OUTPUT_MAX_BYTES {
                return true;
            }
        }
    }
    false
}

pub(super) fn bounded_hash_edit_output(text: &str) -> String {
    if text.len() <= HASH_EDIT_OUTPUT_MAX_BYTES {
        return text.to_string();
    }
    let mut end = HASH_EDIT_OUTPUT_MAX_BYTES;
    while !text.is_char_boundary(end) {
        end -= 1;
    }
    format!(
        "{}\n[truncated: hash_edit output exceeded {HASH_EDIT_OUTPUT_MAX_BYTES} bytes]",
        &text[..end]
    )
}