files: make diff() return an iterator instead of using a callback

Iterators are generally nicer to work with. My immediate goal is to be
able to propagate errors when failing to write to stdout.
This commit is contained in:
Martin von Zweigbergk 2021-04-06 23:33:38 -07:00
parent f22514f7bb
commit f634ff0e3f
2 changed files with 88 additions and 48 deletions

View file

@ -12,10 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::VecDeque;
use std::fmt::{Debug, Error, Formatter};
use std::ops::Range;
use crate::diff;
use crate::diff::SliceDiff;
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DiffHunk<'a> {
@ -47,58 +49,96 @@ impl DiffLine<'_> {
}
}
pub fn diff<'a>(left: &'a [u8], right: &'a [u8], callback: &mut impl FnMut(&DiffLine<'a>)) {
// TODO: Should we attempt to interpret as utf-8 and otherwise break only at
// newlines?
let mut diff_line = DiffLine {
left_line_number: 1,
right_line_number: 1,
has_left_content: false,
has_right_content: false,
hunks: vec![],
};
for hunk in diff::diff(left, right) {
match hunk {
diff::SliceDiff::Unchanged(text) => {
let lines = text.split_inclusive(|b| *b == b'\n');
for line in lines {
diff_line.has_left_content = true;
diff_line.has_right_content = true;
diff_line.hunks.push(DiffHunk::Unmodified(line));
if line.ends_with(b"\n") {
callback(&diff_line);
diff_line.left_line_number += 1;
diff_line.right_line_number += 1;
diff_line.reset_line();
pub fn diff<'a>(left: &'a [u8], right: &'a [u8]) -> DiffLineIterator<'a> {
let slice_diffs = diff::diff(left, right);
DiffLineIterator::new(slice_diffs)
}
pub struct DiffLineIterator<'a> {
slice_diffs: Vec<SliceDiff<'a>>,
current_pos: usize,
current_line: DiffLine<'a>,
queued_lines: VecDeque<DiffLine<'a>>,
}
impl<'a> DiffLineIterator<'a> {
fn new(slice_diffs: Vec<SliceDiff<'a>>) -> Self {
let current_line = DiffLine {
left_line_number: 1,
right_line_number: 1,
has_left_content: false,
has_right_content: false,
hunks: vec![],
};
DiffLineIterator {
slice_diffs,
current_pos: 0,
current_line,
queued_lines: VecDeque::new(),
}
}
}
impl<'a> Iterator for DiffLineIterator<'a> {
type Item = DiffLine<'a>;
fn next(&mut self) -> Option<Self::Item> {
// TODO: Should we attempt to interpret as utf-8 and otherwise break only at
// newlines?
while self.current_pos < self.slice_diffs.len() && self.queued_lines.is_empty() {
let hunk = &self.slice_diffs[self.current_pos];
self.current_pos += 1;
match hunk {
diff::SliceDiff::Unchanged(text) => {
let lines = text.split_inclusive(|b| *b == b'\n');
for line in lines {
self.current_line.has_left_content = true;
self.current_line.has_right_content = true;
self.current_line.hunks.push(DiffHunk::Unmodified(line));
if line.ends_with(b"\n") {
self.queued_lines.push_back(self.current_line.clone());
self.current_line.left_line_number += 1;
self.current_line.right_line_number += 1;
self.current_line.reset_line();
}
}
}
}
diff::SliceDiff::Replaced(left, right) => {
let left_lines = left.split_inclusive(|b| *b == b'\n');
for left_line in left_lines {
diff_line.has_left_content = true;
diff_line.hunks.push(DiffHunk::Removed(left_line));
if left_line.ends_with(b"\n") {
callback(&diff_line);
diff_line.left_line_number += 1;
diff_line.reset_line();
diff::SliceDiff::Replaced(left, right) => {
let left_lines = left.split_inclusive(|b| *b == b'\n');
for left_line in left_lines {
self.current_line.has_left_content = true;
self.current_line.hunks.push(DiffHunk::Removed(left_line));
if left_line.ends_with(b"\n") {
self.queued_lines.push_back(self.current_line.clone());
self.current_line.left_line_number += 1;
self.current_line.reset_line();
}
}
}
let right_lines = right.split_inclusive(|b| *b == b'\n');
for right_line in right_lines {
diff_line.has_right_content = true;
diff_line.hunks.push(DiffHunk::Added(right_line));
if right_line.ends_with(b"\n") {
callback(&diff_line);
diff_line.right_line_number += 1;
diff_line.reset_line();
let right_lines = right.split_inclusive(|b| *b == b'\n');
for right_line in right_lines {
self.current_line.has_right_content = true;
self.current_line.hunks.push(DiffHunk::Added(right_line));
if right_line.ends_with(b"\n") {
self.queued_lines.push_back(self.current_line.clone());
self.current_line.right_line_number += 1;
self.current_line.reset_line();
}
}
}
}
}
}
if !diff_line.hunks.is_empty() {
callback(&diff_line);
if let Some(line) = self.queued_lines.pop_front() {
return Some(line);
}
if !self.current_line.hunks.is_empty() {
let line = self.current_line.clone();
self.current_line.reset_line();
return Some(line);
}
None
}
}

View file

@ -687,7 +687,7 @@ fn print_diff(left: &[u8], right: &[u8], styler: &mut dyn Styler) {
let mut skipped_context = false;
// Are the lines in `context` to be printed before the next modified line?
let mut context_before = true;
files::diff(left, right, &mut |diff_line| {
for diff_line in files::diff(left, right) {
if diff_line.is_unmodified() {
context.push_back(diff_line.clone());
if context.len() > num_context_lines {
@ -715,11 +715,11 @@ fn print_diff(left: &[u8], right: &[u8], styler: &mut dyn Styler) {
}
}
context.clear();
print_diff_line(styler, diff_line);
print_diff_line(styler, &diff_line);
context_before = false;
skipped_context = false;
}
});
}
if !context_before {
for line in &context {
print_diff_line(styler, line);