atomwrite 0.1.38

Atomic file operations CLI for LLM agents — read, write, edit, search, replace with NDJSON output
/// Apply a line-range replacement preserving trailing newline of `original`.
pub fn apply_line_replacement(
    original: &str,
    content_lines: &[&str],
    start: usize,
    end: usize,
    new: &str,
) -> String {
    // G-FZZ-037: preserve dominant line ending (CRLF vs LF) instead of always joining with LF.
    let nl = if original.contains("\r\n") {
        "\r\n"
    } else {
        "\n"
    };
    let before = content_lines[..start].join(nl);
    let after = content_lines[end..].join(nl);
    // Normalize `new` internal newlines to the file's dominant ending.
    let new_norm = if nl == "\r\n" {
        new.replace("\r\n", "\n").replace('\n', "\r\n")
    } else {
        new.replace("\r\n", "\n")
    };
    let mut out = String::with_capacity(
        before.len() + new_norm.len() + after.len() + crate::constants::FUZZY_LINES_SLACK_BYTES,
    );
    // The separator belongs between two surviving pieces. An empty `new`
    // leaves no piece of its own, so joining `before` and `after` with one
    // newline is what deletes the range instead of leaving a blank line.
    out.push_str(&before);
    if !before.is_empty() && (!new_norm.is_empty() || !after.is_empty()) {
        out.push_str(nl);
    }
    out.push_str(&new_norm);
    if !after.is_empty() && (!new_norm.is_empty() || before.is_empty()) {
        out.push_str(nl);
    }
    out.push_str(&after);
    let ends_nl = original.ends_with('\n');
    if ends_nl && !out.is_empty() && !out.ends_with('\n') {
        out.push_str(nl);
    }
    out
}