1use std::fmt::{self, Display};
2use std::hash::Hash;
3
4use crate::intern::{InternedInput, Interner, Token};
5use crate::Diff;
6
7impl Diff {
8 pub fn unified_diff<'a, P: UnifiedDiffPrinter, T: Hash + Eq>(
9 &'a self,
10 printer: &'a P,
11 config: UnifiedDiffConfig,
12 input: &'a InternedInput<T>,
13 ) -> UnifiedDiff<'a, P> {
14 self.unified_diff_with(printer, config, &input.before, &input.after)
15 }
16
17 pub fn unified_diff_with<'a, P: UnifiedDiffPrinter>(
18 &'a self,
19 printer: &'a P,
20 config: UnifiedDiffConfig,
21 before: &'a [Token],
22 after: &'a [Token],
23 ) -> UnifiedDiff<'a, P> {
24 UnifiedDiff {
25 printer,
26 diff: self,
27 config,
28 before,
29 after,
30 }
31 }
32}
33
34pub trait UnifiedDiffPrinter {
35 fn display_header(
36 &self,
37 f: impl fmt::Write,
38 start_before: u32,
39 start_after: u32,
40 len_before: u32,
41 len_after: u32,
42 ) -> fmt::Result;
43 fn display_context_token(&self, f: impl fmt::Write, token: Token) -> fmt::Result;
44 fn display_hunk(&self, f: impl fmt::Write, before: &[Token], after: &[Token]) -> fmt::Result;
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct UnifiedDiffConfig {
49 context_len: u32,
50}
51
52impl Default for UnifiedDiffConfig {
53 fn default() -> Self {
54 UnifiedDiffConfig { context_len: 3 }
55 }
56}
57
58impl UnifiedDiffConfig {
59 pub fn context_len(&mut self, len: u32) -> &mut Self {
60 self.context_len = len;
61 self
62 }
63}
64
65pub trait EndsWithNewline {
66 fn ends_with_newline(&self) -> bool;
67}
68
69impl<T: AsRef<[u8]> + ?Sized> EndsWithNewline for T {
70 fn ends_with_newline(&self) -> bool {
71 self.as_ref().ends_with(b"\n")
72 }
73}
74
75pub struct BasicLineDiffPrinter<'a, T: EndsWithNewline + ?Sized + Hash + Eq + Display>(
76 pub &'a Interner<&'a T>,
77);
78
79impl<T: EndsWithNewline + Hash + Eq + Display + ?Sized> UnifiedDiffPrinter
80 for BasicLineDiffPrinter<'_, T>
81{
82 fn display_header(
83 &self,
84 mut f: impl fmt::Write,
85 start_before: u32,
86 start_after: u32,
87 len_before: u32,
88 len_after: u32,
89 ) -> fmt::Result {
90 writeln!(
91 f,
92 "@@ -{},{} +{},{} @@",
93 start_before + 1,
94 len_before,
95 start_after + 1,
96 len_after
97 )
98 }
99
100 fn display_context_token(&self, mut f: impl fmt::Write, token: Token) -> fmt::Result {
101 write!(f, " {}", &self.0[token])?;
102 if !&self.0[token].ends_with_newline() {
103 writeln!(f)?;
104 }
105 Ok(())
106 }
107
108 fn display_hunk(
109 &self,
110 mut f: impl fmt::Write,
111 before: &[Token],
112 after: &[Token],
113 ) -> fmt::Result {
114 if let Some(&last) = before.last() {
115 for &token in before {
116 let token = self.0[token];
117 write!(f, "-{token}")?;
118 }
119 if !self.0[last].ends_with_newline() {
120 writeln!(f)?;
121 }
122 }
123 if let Some(&last) = after.last() {
124 for &token in after {
125 let token = self.0[token];
126 write!(f, "+{token}")?;
127 }
128 if !self.0[last].ends_with_newline() {
129 writeln!(f)?;
130 }
131 }
132 Ok(())
133 }
134}
135
136pub struct UnifiedDiff<'a, P: UnifiedDiffPrinter> {
137 printer: &'a P,
138 diff: &'a Diff,
139 config: UnifiedDiffConfig,
140 before: &'a [Token],
141 after: &'a [Token],
142}
143
144impl<P: UnifiedDiffPrinter> Display for UnifiedDiff<'_, P> {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 let mut pos = 0;
147 let mut before_context_len = 0;
148 let mut after_context_len = 0;
149 let first_hunk = self.diff.hunks().next().unwrap_or_default();
150 let mut before_context_start = first_hunk
151 .before
152 .start
153 .saturating_sub(self.config.context_len);
154 let mut after_context_start = first_hunk
155 .after
156 .start
157 .saturating_sub(self.config.context_len);
158 let mut buffer = String::new();
159 for hunk in self.diff.hunks() {
160 if hunk.before.start - pos > 2 * self.config.context_len {
161 if !buffer.is_empty() {
162 let end = (pos + self.config.context_len).min(self.before.len() as u32);
163 self.printer.display_header(
164 &mut *f,
165 before_context_start,
166 after_context_start,
167 before_context_len + end - pos,
168 after_context_len + end - pos,
169 )?;
170 write!(f, "{buffer}")?;
171 for &token in &self.before[pos as usize..end as usize] {
172 self.printer.display_context_token(&mut *f, token)?;
173 }
174 buffer.clear();
175 }
176 pos = hunk.before.start - self.config.context_len;
177 before_context_start = pos;
178 after_context_start = hunk.after.start - self.config.context_len;
179 before_context_len = 0;
180 after_context_len = 0;
181 }
182 for &token in &self.before[pos as usize..hunk.before.start as usize] {
183 self.printer.display_context_token(&mut buffer, token)?;
184 }
185 let context_len = hunk.before.start - pos;
186 before_context_len += hunk.before.len() as u32 + context_len;
187 after_context_len += hunk.after.len() as u32 + context_len;
188 self.printer.display_hunk(
189 &mut buffer,
190 &self.before[hunk.before.start as usize..hunk.before.end as usize],
191 &self.after[hunk.after.start as usize..hunk.after.end as usize],
192 )?;
193 pos = hunk.before.end;
194 }
195 if !buffer.is_empty() {
196 let end = (pos + self.config.context_len).min(self.before.len() as u32);
197 self.printer.display_header(
198 &mut *f,
199 before_context_start,
200 after_context_start,
201 before_context_len + end - pos,
202 after_context_len + end - pos,
203 )?;
204 write!(f, "{buffer}")?;
205 for &token in &self.before[pos as usize..end as usize] {
206 self.printer.display_context_token(&mut *f, token)?;
207 }
208 buffer.clear();
209 }
210 Ok(())
211 }
212}