Add DiffOps playground

This commit is contained in:
2026-05-23 12:28:45 +01:00
parent 553af0290f
commit 1ef91cb41e
7 changed files with 961 additions and 43 deletions

184
src/util/diff.rs Normal file
View File

@@ -0,0 +1,184 @@
use std::ops::Range;
pub(crate) struct ContentDiff {
pub(crate) old_content: bytes::Bytes,
pub(crate) new_content: bytes::Bytes,
pub(crate) spans: Vec<Span>,
old_line_ranges: Vec<Range<usize>>,
new_line_ranges: Vec<Range<usize>>,
}
pub(crate) struct Span {
pub(crate) op: Op,
pub(crate) old_range: Range<usize>,
pub(crate) new_range: Range<usize>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Op {
Equal,
Delete,
Insert,
Replace,
}
#[derive(Clone, Copy)]
pub(crate) enum DiffSide {
Old,
New,
}
pub(crate) struct DiffRow {
pub(crate) op_index: usize,
pub(crate) op: Op,
pub(crate) old_line: Option<usize>,
pub(crate) old_content_range: Option<Range<usize>>,
pub(crate) new_line: Option<usize>,
pub(crate) new_content_range: Option<Range<usize>>,
}
pub(crate) fn diff_content(old_content: bytes::Bytes, new_content: bytes::Bytes) -> ContentDiff {
let old_line_ranges = line_ranges(&old_content);
let new_line_ranges = line_ranges(&new_content);
let diff = similar::TextDiff::from_lines::<[u8]>(&old_content, &new_content);
let spans = diff
.ops()
.iter()
.map(|op| match op {
| &similar::DiffOp::Equal {
old_index,
new_index,
len,
} => Span {
op: Op::Equal,
old_range: old_index..(old_index + len),
new_range: new_index..(new_index + len),
},
| &similar::DiffOp::Delete {
old_index,
old_len,
new_index,
} => Span {
op: Op::Delete,
old_range: old_index..(old_index + old_len),
new_range: new_index..new_index,
},
| &similar::DiffOp::Insert {
old_index,
new_index,
new_len,
} => Span {
op: Op::Insert,
old_range: old_index..old_index,
new_range: new_index..(new_index + new_len),
},
| &similar::DiffOp::Replace {
old_index,
old_len,
new_index,
new_len,
} => Span {
op: Op::Replace,
old_range: old_index..(old_index + old_len),
new_range: new_index..(new_index + new_len),
},
})
.collect();
ContentDiff {
old_content,
new_content,
spans,
old_line_ranges,
new_line_ranges,
}
}
impl ContentDiff {
pub(crate) fn spans(&self) -> &[Span] {
&self.spans
}
pub(crate) fn old_line_count(&self) -> usize {
self.old_line_ranges.len()
}
pub(crate) fn new_line_count(&self) -> usize {
self.new_line_ranges.len()
}
pub(crate) fn line_slice(&self, side: DiffSide, range: &Range<usize>) -> &[u8] {
match side {
| DiffSide::Old => &self.old_content[range.clone()],
| DiffSide::New => &self.new_content[range.clone()],
}
}
pub(crate) fn line_slice_at(&self, side: DiffSide, line: usize) -> &[u8] {
match side {
| DiffSide::Old => self.line_slice(DiffSide::Old, &self.old_line_ranges[line]),
| DiffSide::New => self.line_slice(DiffSide::New, &self.new_line_ranges[line]),
}
}
pub(crate) fn rows_for_span(&self, span_index: usize) -> Vec<DiffRow> {
let span = &self.spans[span_index];
let old_len = span.old_range.end.saturating_sub(span.old_range.start);
let new_len = span.new_range.end.saturating_sub(span.new_range.start);
let row_count = old_len.max(new_len);
let mut rows = Vec::with_capacity(row_count);
for offset in 0..row_count {
let old_line = (offset < old_len).then_some(span.old_range.start + offset);
let new_line = (offset < new_len).then_some(span.new_range.start + offset);
rows.push(DiffRow {
op_index: span_index,
op: span.op,
old_line,
old_content_range: old_line.map(|line| self.old_line_ranges[line].clone()),
new_line,
new_content_range: new_line.map(|line| self.new_line_ranges[line].clone()),
});
}
rows
}
}
fn line_ranges(content: &[u8]) -> Vec<Range<usize>> {
let mut ranges = Vec::new();
let mut start = 0;
let mut index = 0;
while index < content.len() {
match content[index] {
| b'\r' => {
index += 1;
if index < content.len() && content[index] == b'\n' {
index += 1;
}
ranges.push(start..index);
start = index;
}
| b'\n' => {
index += 1;
ranges.push(start..index);
start = index;
}
| _ => {
index += 1;
}
}
}
if start < content.len() {
ranges.push(start..content.len());
}
ranges
}

View File

@@ -1,2 +1,3 @@
pub(crate) mod diff;
pub(crate) mod file;
pub(crate) mod timeout;