Add DiffOps playground
This commit is contained in:
184
src/util/diff.rs
Normal file
184
src/util/diff.rs
Normal 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
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub(crate) mod diff;
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod timeout;
|
||||
|
||||
Reference in New Issue
Block a user