use crate::core::tokens::count_tokens;
use super::anchors::AnchorOp;
pub(crate) fn avoided_output_tokens(lines: &[String], ops: &[AnchorOp]) -> u64 {
ops.iter().map(|op| op_avoided_tokens(lines, op)).sum()
}
fn op_avoided_tokens(lines: &[String], op: &AnchorOp) -> u64 {
let span_tokens = |lo: usize, hi: usize| -> usize {
if lo == 0 || lo > hi || hi > lines.len() {
return 0;
}
count_tokens(&lines[lo - 1..hi].join("\n"))
};
let (span, anchor_args) = match op {
AnchorOp::SetLine { line, hash, .. } => {
(span_tokens(*line, *line), format!("set_line {line}:{hash}"))
}
AnchorOp::ReplaceLines {
start_line,
start_hash,
end_line,
end_hash,
..
} => (
span_tokens(*start_line, *end_line),
format!("replace_lines {start_line}:{start_hash}-{end_line}:{end_hash}"),
),
AnchorOp::Delete {
start_line,
start_hash,
end_line,
end_hash,
} => (
span_tokens(*start_line, *end_line),
format!("delete {start_line}:{start_hash}-{end_line}:{end_hash}"),
),
AnchorOp::InsertAfter { line, hash, .. } => (
span_tokens(*line, *line),
format!("insert_after {line}:{}", hash.as_deref().unwrap_or("")),
),
AnchorOp::Create { .. } => (0, String::new()),
};
(span as u64).saturating_sub(count_tokens(&anchor_args) as u64)
}
#[cfg(test)]
mod tests {
use super::*;
fn lines(v: &[&str]) -> Vec<String> {
v.iter().map(ToString::to_string).collect()
}
#[test]
fn replace_lines_counts_span_minus_anchor() {
let l = lines(&[
"fn compute_totals(items: &[Item]) -> Totals {",
" let mut sum = 0u64;",
" for item in items { sum += item.value; }",
" Totals { sum, count: items.len() }",
"}",
]);
let op = AnchorOp::ReplaceLines {
start_line: 2,
start_hash: "ab12".into(),
end_line: 4,
end_hash: "cd34".into(),
new_text: " Totals::from(items)".into(),
};
let span = count_tokens(&l[1..4].join("\n")) as u64;
let anchor = count_tokens("replace_lines 2:ab12-4:cd34") as u64;
assert_eq!(avoided_output_tokens(&l, &[op]), span - anchor);
}
#[test]
fn tiny_span_never_goes_negative() {
let l = lines(&["x", "y"]);
let op = AnchorOp::SetLine {
line: 1,
hash: "ab12".into(),
new_text: "z".into(),
};
assert_eq!(avoided_output_tokens(&l, &[op]), 0);
}
#[test]
fn create_and_top_insert_avoid_nothing() {
let l = lines(&["a", "b"]);
assert_eq!(
avoided_output_tokens(
&l,
&[AnchorOp::Create {
new_text: "whole new file".into()
}]
),
0
);
assert_eq!(
avoided_output_tokens(
&l,
&[AnchorOp::InsertAfter {
line: 0,
hash: None,
new_text: "// header".into()
}]
),
0
);
}
#[test]
fn batch_sums_per_op() {
let long = " let result = some_function_call(with, many, arguments) + another_call();";
let l = lines(&[long, long, long]);
let mk = |line: usize| AnchorOp::SetLine {
line,
hash: "ab12".into(),
new_text: " let result = simplified();".into(),
};
let one = avoided_output_tokens(&l, &[mk(1)]);
let two = avoided_output_tokens(&l, &[mk(1), mk(3)]);
assert!(one > 0, "long line must avoid tokens");
assert_eq!(two, one * 2);
}
}