A change on 2x context lines will have two much context from the top.
Unified · Split
| @@ -77,6 +77,10 @@ pub(super) mod _impl { · 76 unmodified lines | |||
| 77 | 77 | ||
| 78 | use super::{ConsumeHunk, ContextSize, NewlineSeparator}; | 78 | use super::{ConsumeHunk, ContextSize, NewlineSeparator}; |
| 79 | 79 | ||
| 80 | const CONTEXT: char = ' '; | ||
| 81 | const ADDITION: char = '+'; | ||
| 82 | const REMOVAL: char = '-'; | ||
| 83 | |||
| 80 | /// A [`Sink`] that creates a textual diff in the format typically output by git or `gnu-diff` if the `-u` option is used, | 84 | /// A [`Sink`] that creates a textual diff in the format typically output by git or `gnu-diff` if the `-u` option is used, |
| 81 | /// and passes it in full to a consumer. | 85 | /// and passes it in full to a consumer. |
| 82 | pub struct UnifiedDiff<'a, T, D> | 86 | pub struct UnifiedDiff<'a, T, D> |
| @@ -88,18 +92,25 @@ pub(super) mod _impl { · 5 unmodified lines | |||
| 88 | after: &'a [Token], | 92 | after: &'a [Token], |
| 89 | interner: &'a Interner<T>, | 93 | interner: &'a Interner<T>, |
| 90 | 94 | ||
| 91 | pos: u32, | 95 | /// The 0-based start position in the 'before' tokens for the accumulated hunk for display in the header. |
| 92 | before_hunk_start: u32, | 96 | before_hunk_start: u32, |
| 93 | after_hunk_start: u32, | 97 | /// The size of the accumulated 'before' hunk in lines for display in the header. |
| 94 | before_hunk_len: u32, | 98 | before_hunk_len: u32, |
| 99 | /// The 0-based start position in the 'after' tokens for the accumulated hunk for display in the header. | ||
| 100 | after_hunk_start: u32, | ||
| 101 | /// The size of the accumulated 'after' hunk in lines. | ||
| 95 | after_hunk_len: u32, | 102 | after_hunk_len: u32, |
| 103 | // An index into `before` and the context line to print next, | ||
| 104 | // or `None` if this value was never computed to be the correct starting point for an accumulated hunk. | ||
| 105 | ctx_pos: Option<u32>, | ||
| 106 | |||
| 96 | /// Symmetrical context before and after the changed hunk. | 107 | /// Symmetrical context before and after the changed hunk. |
| 97 | ctx_size: u32, | 108 | ctx_size: u32, |
| 109 | newline: NewlineSeparator<'a>, | ||
| 98 | 110 | ||
| 99 | buffer: Vec<u8>, | 111 | buffer: Vec<u8>, |
| 100 | header_buf: String, | 112 | header_buf: String, |
| 101 | delegate: D, | 113 | delegate: D, |
| 102 | newline: NewlineSeparator<'a>, | ||
| 103 | 114 | ||
| 104 | err: Option<std::io::Error>, | 115 | err: Option<std::io::Error>, |
| 105 | } | 116 | } |
| @@ -122,19 +133,22 @@ pub(super) mod _impl { · 16 unmodified lines | |||
| 122 | context_size: ContextSize, | 133 | context_size: ContextSize, |
| 123 | ) -> Self { | 134 | ) -> Self { |
| 124 | Self { | 135 | Self { |
| 136 | interner: &input.interner, | ||
| 137 | before: &input.before, | ||
| 138 | after: &input.after, | ||
| 139 | |||
| 125 | before_hunk_start: 0, | 140 | before_hunk_start: 0, |
| 126 | after_hunk_start: 0, | ||
| 127 | before_hunk_len: 0, | 141 | before_hunk_len: 0, |
| 128 | after_hunk_len: 0, | 142 | after_hunk_len: 0, |
| 143 | after_hunk_start: 0, | ||
| 144 | ctx_pos: None, | ||
| 145 | |||
| 146 | ctx_size: context_size.symmetrical, | ||
| 147 | newline: newline_separator, | ||
| 148 | |||
| 129 | buffer: Vec::with_capacity(8), | 149 | buffer: Vec::with_capacity(8), |
| 130 | header_buf: String::new(), | 150 | header_buf: String::new(), |
| 131 | delegate: consume_hunk, | 151 | delegate: consume_hunk, |
| 132 | interner: &input.interner, | ||
| 133 | before: &input.before, | ||
| 134 | after: &input.after, | ||
| 135 | pos: 0, | ||
| 136 | ctx_size: context_size.symmetrical, | ||
| 137 | newline: newline_separator, | ||
| 138 | 152 | ||
| 139 | err: None, | 153 | err: None, |
| 140 | } | 154 | } |
| @@ -158,23 +172,25 @@ pub(super) mod _impl { · 17 unmodified lines | |||
| 158 | } | 172 | } |
| 159 | } | 173 | } |
| 160 | 174 | ||
| 161 | fn flush(&mut self) -> std::io::Result<()> { | 175 | fn flush_accumulated_hunk(&mut self) -> std::io::Result<()> { |
| 162 | if self.before_hunk_len == 0 && self.after_hunk_len == 0 { | 176 | if self.nothing_to_flush() { |
| 163 | return Ok(()); | 177 | return Ok(()); |
| 164 | } | 178 | } |
| 165 | 179 | ||
| 166 | let end = (self.pos + self.ctx_size).min(self.before.len() as u32); | 180 | let ctx_pos = self.ctx_pos.expect("has been set if we started a hunk"); |
| 167 | self.update_pos(end, end); | 181 | let end = (ctx_pos + self.ctx_size).min(self.before.len() as u32); |
| 182 | self.print_context_and_update_pos(ctx_pos..end, end); | ||
| 168 | 183 | ||
| 184 | let hunk_start = self.before_hunk_start + 1; | ||
| 185 | let hunk_end = self.after_hunk_start + 1; | ||
| 169 | self.header_buf.clear(); | 186 | self.header_buf.clear(); |
| 170 | |||
| 171 | std::fmt::Write::write_fmt( | 187 | std::fmt::Write::write_fmt( |
| 172 | &mut self.header_buf, | 188 | &mut self.header_buf, |
| 173 | format_args!( | 189 | format_args!( |
| 174 | "@@ -{},{} +{},{} @@{nl}", | 190 | "@@ -{},{} +{},{} @@{nl}", |
| 175 | self.before_hunk_start + 1, | 191 | hunk_start, |
| 176 | self.before_hunk_len, | 192 | self.before_hunk_len, |
| 177 | self.after_hunk_start + 1, | 193 | hunk_end, |
| 178 | self.after_hunk_len, | 194 | self.after_hunk_len, |
| 179 | nl = match self.newline { | 195 | nl = match self.newline { |
| 180 | NewlineSeparator::AfterHeaderAndLine(nl) | NewlineSeparator::AfterHeaderAndWhenNeeded(nl) => { | 196 | NewlineSeparator::AfterHeaderAndLine(nl) | NewlineSeparator::AfterHeaderAndWhenNeeded(nl) => { |
| @@ -185,26 +201,35 @@ pub(super) mod _impl { · 4 unmodified lines | |||
| 185 | ) | 201 | ) |
| 186 | .map_err(|err| std::io::Error::new(ErrorKind::Other, err))?; | 202 | .map_err(|err| std::io::Error::new(ErrorKind::Other, err))?; |
| 187 | self.delegate.consume_hunk( | 203 | self.delegate.consume_hunk( |
| 188 | self.before_hunk_start + 1, | 204 | hunk_start, |
| 189 | self.before_hunk_len, | 205 | self.before_hunk_len, |
| 190 | self.after_hunk_start + 1, | 206 | hunk_end, |
| 191 | self.after_hunk_len, | 207 | self.after_hunk_len, |
| 192 | &self.header_buf, | 208 | &self.header_buf, |
| 193 | &self.buffer, | 209 | &self.buffer, |
| 194 | )?; | 210 | )?; |
| 195 | self.buffer.clear(); | 211 | |
| 196 | self.before_hunk_len = 0; | 212 | self.reset_hunks(); |
| 197 | self.after_hunk_len = 0; | ||
| 198 | Ok(()) | 213 | Ok(()) |
| 199 | } | 214 | } |
| 200 | 215 | ||
| 201 | fn update_pos(&mut self, print_to: u32, move_to: u32) { | 216 | fn print_context_and_update_pos(&mut self, print: Range<u32>, move_to: u32) { |
| 202 | self.print_tokens(&self.before[self.pos as usize..print_to as usize], ' '); | 217 | self.print_tokens(&self.before[print.start as usize..print.end as usize], CONTEXT); |
| 203 | let len = print_to - self.pos; | 218 | let len = print.end - print.start; |
| 204 | self.pos = move_to; | 219 | self.ctx_pos = Some(move_to); |
| 205 | self.before_hunk_len += len; | 220 | self.before_hunk_len += len; |
| 206 | self.after_hunk_len += len; | 221 | self.after_hunk_len += len; |
| 207 | } | 222 | } |
| 223 | |||
| 224 | fn reset_hunks(&mut self) { | ||
| 225 | self.buffer.clear(); | ||
| 226 | self.before_hunk_len = 0; | ||
| 227 | self.after_hunk_len = 0; | ||
| 228 | } | ||
| 229 | |||
| 230 | fn nothing_to_flush(&self) -> bool { | ||
| 231 | self.before_hunk_len == 0 && self.after_hunk_len == 0 | ||
| 232 | } | ||
| 208 | } | 233 | } |
| 209 | 234 | ||
| 210 | impl<T, D> Sink for UnifiedDiff<'_, T, D> | 235 | impl<T, D> Sink for UnifiedDiff<'_, T, D> |
| @@ -218,24 +243,39 @@ pub(super) mod _impl { · 7 unmodified lines | |||
| 218 | if self.err.is_some() { | 243 | if self.err.is_some() { |
| 219 | return; | 244 | return; |
| 220 | } | 245 | } |
| 221 | if before.start - self.pos > 2 * self.ctx_size { | 246 | let start_next_hunk = self |
| 222 | if let Err(err) = self.flush() { | 247 | .ctx_pos |
| 248 | .is_some_and(|ctx_pos| before.start - ctx_pos > 2 * self.ctx_size); | ||
| 249 | if start_next_hunk { | ||
| 250 | if let Err(err) = self.flush_accumulated_hunk() { | ||
| 223 | self.err = Some(err); | 251 | self.err = Some(err); |
| 224 | return; | 252 | return; |
| 225 | } | 253 | } |
| 226 | self.pos = before.start - self.ctx_size; | 254 | let ctx_pos = before.start - self.ctx_size; |
| 227 | self.before_hunk_start = self.pos; | 255 | self.ctx_pos = Some(ctx_pos); |
| 256 | self.before_hunk_start = ctx_pos; | ||
| 228 | self.after_hunk_start = after.start - self.ctx_size; | 257 | self.after_hunk_start = after.start - self.ctx_size; |
| 229 | } | 258 | } |
| 230 | self.update_pos(before.start, before.end); | 259 | let ctx_pos = match self.ctx_pos { |
| 260 | None => { | ||
| 261 | // TODO: can this be made so the code above does the job? | ||
| 262 | let ctx_pos = before.start.saturating_sub(self.ctx_size); | ||
| 263 | self.before_hunk_start = ctx_pos; | ||
| 264 | self.after_hunk_start = after.start.saturating_sub(self.ctx_size); | ||
| 265 | ctx_pos | ||
| 266 | } | ||
| 267 | Some(pos) => pos, | ||
| 268 | }; | ||
| 269 | self.print_context_and_update_pos(ctx_pos..before.start, before.end); | ||
| 231 | self.before_hunk_len += before.end - before.start; | 270 | self.before_hunk_len += before.end - before.start; |
| 232 | self.after_hunk_len += after.end - after.start; | 271 | self.after_hunk_len += after.end - after.start; |
| 233 | self.print_tokens(&self.before[before.start as usize..before.end as usize], '-'); | 272 | |
| 234 | self.print_tokens(&self.after[after.start as usize..after.end as usize], '+'); | 273 | self.print_tokens(&self.before[before.start as usize..before.end as usize], REMOVAL); |
| 274 | self.print_tokens(&self.after[after.start as usize..after.end as usize], ADDITION); | ||
| 235 | } | 275 | } |
| 236 | 276 | ||
| 237 | fn finish(mut self) -> Self::Out { | 277 | fn finish(mut self) -> Self::Out { |
| 238 | if let Err(err) = self.flush() { | 278 | if let Err(err) = self.flush_accumulated_hunk() { |
| 239 | self.err = Some(err); | 279 | self.err = Some(err); |
| 240 | } | 280 | } |
| 241 | if let Some(err) = self.err { | 281 | if let Some(err) = self.err { |
| @@ -170,6 +170,125 @@ fn context_overlap_by_one_line_move_down() -> crate::Result { · 169 unmodified lines | |||
| 170 | Ok(()) | 170 | Ok(()) |
| 171 | } | 171 | } |
| 172 | 172 | ||
| 173 | #[test] | ||
| 174 | fn added_on_top_keeps_context_correctly_sized() -> crate::Result { | ||
| 175 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; | ||
| 176 | let b = "1\n2\n3\n4\n4.5\n5\n6\n7\n8\n9\n10"; | ||
| 177 | |||
| 178 | let a = gix_diff::blob::sources::lines_with_terminator(a); | ||
| 179 | let b = gix_diff::blob::sources::lines_with_terminator(b); | ||
| 180 | let interner = gix_diff::blob::intern::InternedInput::new(a, b); | ||
| 181 | |||
| 182 | let actual = gix_diff::blob::diff( | ||
| 183 | Algorithm::Myers, | ||
| 184 | &interner, | ||
| 185 | UnifiedDiff::new( | ||
| 186 | &interner, | ||
| 187 | String::new(), | ||
| 188 | NewlineSeparator::AfterHeaderAndWhenNeeded("\n"), | ||
| 189 | ContextSize::symmetrical(3), | ||
| 190 | ), | ||
| 191 | )?; | ||
| 192 | // TODO: fix this | ||
| 193 | insta::assert_snapshot!(actual, @r" | ||
| 194 | @@ -2,6 +2,7 @@ | ||
| 195 | 2 | ||
| 196 | 3 | ||
| 197 | 4 | ||
| 198 | +4.5 | ||
| 199 | 5 | ||
| 200 | 6 | ||
| 201 | 7 | ||
| 202 | "); | ||
| 203 | |||
| 204 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; | ||
| 205 | let b = "1\n2\n3\n4\n5\n6\n6.5\n7\n8\n9\n10"; | ||
| 206 | |||
| 207 | let a = gix_diff::blob::sources::lines_with_terminator(a); | ||
| 208 | let b = gix_diff::blob::sources::lines_with_terminator(b); | ||
| 209 | let interner = gix_diff::blob::intern::InternedInput::new(a, b); | ||
| 210 | |||
| 211 | let actual = gix_diff::blob::diff( | ||
| 212 | Algorithm::Myers, | ||
| 213 | &interner, | ||
| 214 | UnifiedDiff::new( | ||
| 215 | &interner, | ||
| 216 | String::new(), | ||
| 217 | NewlineSeparator::AfterHeaderAndWhenNeeded("\n"), | ||
| 218 | ContextSize::symmetrical(3), | ||
| 219 | ), | ||
| 220 | )?; | ||
| 221 | |||
| 222 | insta::assert_snapshot!(actual, @r" | ||
| 223 | @@ -4,6 +4,7 @@ | ||
| 224 | 4 | ||
| 225 | 5 | ||
| 226 | 6 | ||
| 227 | +6.5 | ||
| 228 | 7 | ||
| 229 | 8 | ||
| 230 | 9 | ||
| 231 | "); | ||
| 232 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; | ||
| 233 | let b = "1\n2\n3\n3.5\n4\n5\n6\n7\n8\n9\n10"; | ||
| 234 | |||
| 235 | let a = gix_diff::blob::sources::lines_with_terminator(a); | ||
| 236 | let b = gix_diff::blob::sources::lines_with_terminator(b); | ||
| 237 | let interner = gix_diff::blob::intern::InternedInput::new(a, b); | ||
| 238 | |||
| 239 | let actual = gix_diff::blob::diff( | ||
| 240 | Algorithm::Myers, | ||
| 241 | &interner, | ||
| 242 | UnifiedDiff::new( | ||
| 243 | &interner, | ||
| 244 | String::new(), | ||
| 245 | NewlineSeparator::AfterHeaderAndWhenNeeded("\n"), | ||
| 246 | ContextSize::symmetrical(3), | ||
| 247 | ), | ||
| 248 | )?; | ||
| 249 | |||
| 250 | insta::assert_snapshot!(actual, @r" | ||
| 251 | @@ -1,6 +1,7 @@ | ||
| 252 | 1 | ||
| 253 | 2 | ||
| 254 | 3 | ||
| 255 | +3.5 | ||
| 256 | 4 | ||
| 257 | 5 | ||
| 258 | 6 | ||
| 259 | "); | ||
| 260 | |||
| 261 | // From the end, for good measure | ||
| 262 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; | ||
| 263 | let b = "1\n2\n3\n4\n5\n6\n7\n7.5\n8\n9\n10"; | ||
| 264 | |||
| 265 | let a = gix_diff::blob::sources::lines_with_terminator(a); | ||
| 266 | let b = gix_diff::blob::sources::lines_with_terminator(b); | ||
| 267 | let interner = gix_diff::blob::intern::InternedInput::new(a, b); | ||
| 268 | |||
| 269 | let actual = gix_diff::blob::diff( | ||
| 270 | Algorithm::Myers, | ||
| 271 | &interner, | ||
| 272 | UnifiedDiff::new( | ||
| 273 | &interner, | ||
| 274 | String::new(), | ||
| 275 | NewlineSeparator::AfterHeaderAndWhenNeeded("\n"), | ||
| 276 | ContextSize::symmetrical(3), | ||
| 277 | ), | ||
| 278 | )?; | ||
| 279 | insta::assert_snapshot!(actual, @r" | ||
| 280 | @@ -5,6 +5,7 @@ | ||
| 281 | 5 | ||
| 282 | 6 | ||
| 283 | 7 | ||
| 284 | +7.5 | ||
| 285 | 8 | ||
| 286 | 9 | ||
| 287 | 10 | ||
| 288 | "); | ||
| 289 | Ok(()) | ||
| 290 | } | ||
| 291 | |||
| 173 | #[test] | 292 | #[test] |
| 174 | fn removed_modified_added_with_newlines_in_tokens() -> crate::Result { | 293 | fn removed_modified_added_with_newlines_in_tokens() -> crate::Result { |
| 175 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; | 294 | let a = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10"; |