gitscope
← rgit / main / src/unified_diff_builder.rs
https://dev.kronoz.win/r/rgit.git
src/unified_diff_builder.rs · Rust
Blame Raw
  1//! Heavily based on [`gix::diff::blob::UnifiedDiffBuilder`] but provides
  2//! a callback that can be used for styling the diffs.
  3
  4use std::{fmt::Write, ops::Range};
  5
  6use gix::diff::blob::{
  7    Sink,
  8    intern::{InternedInput, Interner, Token},
  9};
 10
 11pub(crate) trait Callback {
 12    fn addition(&mut self, data: &str, dst: &mut String);
 13    fn remove(&mut self, data: &str, dst: &mut String);
 14    fn context(&mut self, data: &str, dst: &mut String);
 15}
 16
 17impl<C: Callback> Callback for &mut C {
 18    fn addition(&mut self, data: &str, dst: &mut String) {
 19        (*self).addition(data, dst);
 20    }
 21
 22    fn remove(&mut self, data: &str, dst: &mut String) {
 23        (*self).remove(data, dst);
 24    }
 25
 26    fn context(&mut self, data: &str, dst: &mut String) {
 27        (*self).context(data, dst);
 28    }
 29}
 30
 31/// A [`Sink`] that creates a textual diff
 32/// in the format typically output by git or gnu-diff if the `-u` option is used
 33pub struct UnifiedDiffBuilder<'a, C, W>
 34where
 35    C: Callback,
 36    W: Write,
 37{
 38    before: &'a [Token],
 39    after: &'a [Token],
 40    interner: &'a Interner<&'a str>,
 41
 42    pos: u32,
 43    before_hunk_start: u32,
 44    after_hunk_start: u32,
 45    before_hunk_len: u32,
 46    after_hunk_len: u32,
 47
 48    callback: C,
 49    buffer: String,
 50    dst: W,
 51}
 52
 53impl<'a, C, W> UnifiedDiffBuilder<'a, C, W>
 54where
 55    C: Callback,
 56    W: Write,
 57{
 58    /// Create a new `UnifiedDiffBuilder` for the given `input`,
 59    /// that will writes it output to the provided implementation of [`Write`].
 60    pub fn with_writer(input: &'a InternedInput<&'a str>, writer: W, callback: C) -> Self {
 61        Self {
 62            before_hunk_start: 0,
 63            after_hunk_start: 0,
 64            before_hunk_len: 0,
 65            after_hunk_len: 0,
 66            buffer: String::with_capacity(8),
 67            dst: writer,
 68            interner: &input.interner,
 69            before: &input.before,
 70            after: &input.after,
 71            callback,
 72            pos: 0,
 73        }
 74    }
 75
 76    fn flush(&mut self) {
 77        if self.before_hunk_len == 0 && self.after_hunk_len == 0 {
 78            return;
 79        }
 80
 81        let end = (self.pos + 3).min(u32::try_from(self.before.len()).unwrap_or(u32::MAX));
 82        self.update_pos(end, end);
 83
 84        writeln!(
 85            &mut self.dst,
 86            "@@ -{},{} +{},{} @@",
 87            self.before_hunk_start + 1,
 88            self.before_hunk_len,
 89            self.after_hunk_start + 1,
 90            self.after_hunk_len,
 91        )
 92        .unwrap();
 93        write!(&mut self.dst, "{}", &self.buffer).unwrap();
 94        self.buffer.clear();
 95        self.before_hunk_len = 0;
 96        self.after_hunk_len = 0;
 97    }
 98
 99    fn update_pos(&mut self, print_to: u32, move_to: u32) {
100        for token in &self.before[self.pos as usize..print_to as usize] {
101            self.callback
102                .context(self.interner[*token], &mut self.buffer);
103        }
104        let len = print_to - self.pos;
105        self.pos = move_to;
106        self.before_hunk_len += len;
107        self.after_hunk_len += len;
108    }
109}
110
111impl<C, W> Sink for UnifiedDiffBuilder<'_, C, W>
112where
113    C: Callback,
114    W: Write,
115{
116    type Out = W;
117
118    fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
119        if before.start - self.pos > 6 {
120            self.flush();
121            self.pos = before.start - 3;
122            self.before_hunk_start = self.pos;
123            self.after_hunk_start = after.start - 3;
124        }
125        self.update_pos(before.start, before.end);
126        self.before_hunk_len += before.end - before.start;
127        self.after_hunk_len += after.end - after.start;
128
129        for token in &self.before[before.start as usize..before.end as usize] {
130            self.callback
131                .remove(self.interner[*token], &mut self.buffer);
132        }
133
134        for token in &self.after[after.start as usize..after.end as usize] {
135            self.callback
136                .addition(self.interner[*token], &mut self.buffer);
137        }
138    }
139
140    fn finish(mut self) -> Self::Out {
141        self.flush();
142        self.dst
143    }
144}