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 intern::{InternedInput, Interner, Token},
8 Sink,
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}