From f634ff0e3f3e6138a263ce1af83c04fb49efbbaa Mon Sep 17 00:00:00 2001 From: Martin von Zweigbergk Date: Tue, 6 Apr 2021 23:33:38 -0700 Subject: [PATCH] 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. --- lib/src/files.rs | 130 +++++++++++++++++++++++++++++++---------------- src/commands.rs | 6 +-- 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/lib/src/files.rs b/lib/src/files.rs index 8434e2b4c..9245921f9 100644 --- a/lib/src/files.rs +++ b/lib/src/files.rs @@ -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>, + current_pos: usize, + current_line: DiffLine<'a>, + queued_lines: VecDeque>, +} + +impl<'a> DiffLineIterator<'a> { + fn new(slice_diffs: Vec>) -> 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 { + // 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 } } diff --git a/src/commands.rs b/src/commands.rs index 9eabc1959..df13f0d41 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -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);