use super::apply::Error;
use super::parse::Hunk;
use super::report::HunkReport;
use super::seek::{self, LADDER};
pub(super) fn run_hunks(
text: &str,
hunks: &[Hunk],
path: &str,
) -> Result<(String, Vec<HunkReport>), Error> {
let had_nl = text.ends_with('\n');
let mut lines: Vec<String> = if text.is_empty() {
Vec::new()
} else {
text.split('\n').map(String::from).collect()
};
if had_nl {
lines.pop();
}
let mut cursor = 0usize;
let mut applied = Vec::new();
for (n, hunk) in hunks.iter().enumerate() {
let hunk_no = n + 1;
for anchor in &hunk.anchors {
let what = format!("anchor {anchor:?}");
let (pos, _) = seek::seek(&lines, std::slice::from_ref(anchor), cursor, false)
.map_err(located(path, hunk_no, what))?;
cursor = pos + 1;
}
if hunk.old.is_empty() && !hunk.eof && hunk.anchors.is_empty() {
return Err(Error::UnanchoredInsertion {
path: path.to_string(),
hunk: hunk_no,
});
}
let (pos, rung) = seek::seek(&lines, &hunk.old, cursor, hunk.eof).map_err(located(
path,
hunk_no,
"context".to_string(),
))?;
let matched: Vec<String> = lines[pos..pos + hunk.old.len()].to_vec();
lines.splice(pos..pos + hunk.old.len(), hunk.new.iter().cloned());
cursor = pos + hunk.new.len();
applied.push(HunkReport {
rung: rung.label(),
line: pos + 1,
matched: (rung != LADDER[0]).then_some(matched),
});
}
let mut content = lines.join("\n");
if had_nl && !content.is_empty() {
content.push('\n');
}
Ok((content, applied))
}
fn located(path: &str, hunk: usize, what: String) -> impl FnOnce(seek::Error) -> Error {
let path = path.to_string();
move |source| match source {
seek::Error::NotFound => Error::NotFound {
path,
hunk,
what,
source,
},
seek::Error::Ambiguous { .. } => Error::Ambiguous {
path,
hunk,
what,
source,
},
}
}