Skip to content

Commit 81cece7

Browse files
committed
context diff working
1 parent 416cd79 commit 81cece7

File tree

1 file changed

+216
-58
lines changed

1 file changed

+216
-58
lines changed

text/diff_util/file_diff.rs

Lines changed: 216 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use super::{
1010
use crate::diff_util::constants::NO_NEW_LINE_AT_END_OF_FILE;
1111

1212
use std::{
13+
cmp::Reverse,
1314
collections::HashMap,
1415
fmt::Write,
1516
fs::{read_to_string, File},
@@ -180,7 +181,7 @@ impl<'a> FileDiff<'a> {
180181
self.order_hunks_by_output_format();
181182

182183
if let OutputFormat::Context(context) = self.format_options.output_format {
183-
self.print_context(context);
184+
let _ = self.print_context(context);
184185
} else if let OutputFormat::Unified(unified) = self.format_options.output_format {
185186
let _ = self.print_unified(unified);
186187
} else {
@@ -315,11 +316,12 @@ impl<'a> FileDiff<'a> {
315316
}
316317

317318
fn order_hunks_descending(&mut self) {
318-
self.order_hunks_ascending();
319-
self.hunks.hunks_mut().reverse();
319+
self.hunks
320+
.hunks_mut()
321+
.sort_by_key(|hunk| Reverse((hunk.ln1_end(), hunk.ln2_end())));
320322
}
321323

322-
fn print_context(&mut self, context: usize) {
324+
fn print_context(&mut self, context: usize) -> Result<(), std::fmt::Error> {
323325
println!(
324326
"*** {}",
325327
Self::get_header(self.file1, self.format_options.label1())
@@ -329,70 +331,113 @@ impl<'a> FileDiff<'a> {
329331
Self::get_header(self.file2, self.format_options.label2())
330332
);
331333

332-
for hunk in self.hunks.hunks() {
333-
println!("***************");
334-
let mut hunk_len =
335-
(hunk.ln1_end() - hunk.ln1_start() + 2 * context).min(self.file1.lines().len());
336-
let hunk_start = hunk.ln1_start().saturating_sub(context);
337-
338-
// make sure we don't go out of bounds
339-
if hunk_start + hunk_len > self.file1.lines().len() - 1 {
340-
hunk_len = self.file1.lines().len() - hunk_start;
341-
}
334+
let mut diff_disp = ContextDiffDisplay::new();
342335

343-
println!("*** {},{} ***", hunk_start + 1, hunk_start + hunk_len);
344-
let h1_prefix = if hunk.ln2_start() == hunk.ln2_end() {
345-
"- "
346-
} else {
347-
"! "
348-
};
349-
350-
// dont print context for empty hunk
351-
if hunk.ln1_start() == hunk.ln1_end() {
352-
continue;
353-
}
354-
355-
for i in hunk_start..hunk_start + hunk_len {
356-
if i < hunk.ln1_start() {
357-
println!(" {}", self.file1.line(i));
358-
} else if i < hunk.ln1_end() {
359-
println!("{h1_prefix}{}", self.file1.line(i));
360-
} else {
361-
println!(" {}", self.file1.line(i));
336+
for hunk in self.hunks.hunks() {
337+
// move cursor to the start of context for first hunk
338+
if diff_disp.curr_pos1 == 0 {
339+
if hunk.ln1_start() > context {
340+
// this is guaranteed to be >= 0 due to the above conditions
341+
diff_disp
342+
.update_curr_pos(hunk.ln1_start() - context, hunk.ln2_start() - context);
362343
}
344+
diff_disp.set_context_start();
363345
}
364346

365-
let mut hunk_len =
366-
(hunk.ln2_end() - hunk.ln2_start() + 2 * context).min(self.file2.lines().len());
367-
let hunk_start = hunk.ln2_start().saturating_sub(context);
368-
369-
// make sure we don't go out of bounds
370-
if hunk_start + hunk_len > self.file2.lines().len() - 1 {
371-
hunk_len = self.file2.lines().len() - hunk_start;
347+
// do we have enough context between hunks?
348+
if (diff_disp.curr_pos1 != 0) && (hunk.ln1_start() - diff_disp.curr_pos1 > context * 2)
349+
{
350+
// add context after the previous hunk
351+
diff_disp.write_line(
352+
self.file1,
353+
diff_disp.curr_pos1,
354+
diff_disp.curr_pos1 + context,
355+
" ",
356+
true,
357+
)?;
358+
diff_disp.write_line(
359+
self.file2,
360+
diff_disp.curr_pos2,
361+
diff_disp.curr_pos2 + context,
362+
" ",
363+
false,
364+
)?;
365+
// update current position and print the whole section
366+
diff_disp.update_curr_pos(hunk.ln1_start() - context, hunk.ln2_start() - context);
367+
// check if we have something to display other than just context
368+
let print_lines1 = diff_disp.hunk_lines[0].split('\n').any(|x| x.starts_with('-') || x.starts_with('!'));
369+
diff_disp.print_section(true, print_lines1);
370+
let print_lines2 = diff_disp.hunk_lines[1].split('\n').any(|x| x.starts_with('-') || x.starts_with('!'));
371+
diff_disp.print_section(false, print_lines2);
372372
}
373373

374-
println!("--- {},{} ---", hunk_start + 1, hunk_start + hunk_len);
375-
let h2_prefix = if hunk.ln1_start() == hunk.ln1_end() {
376-
"+ "
374+
// add context before current hunk
375+
diff_disp.write_line(
376+
self.file1,
377+
diff_disp.curr_pos1,
378+
hunk.ln1_start(),
379+
" ",
380+
true,
381+
)?;
382+
// add delete hunk
383+
let h1_prefix = if hunk.ln2_end() - hunk.ln2_start() > 0 {
384+
"! "
377385
} else {
386+
"- "
387+
};
388+
diff_disp.write_line(
389+
self.file1,
390+
hunk.ln1_start(),
391+
hunk.ln1_end(),
392+
h1_prefix,
393+
true,
394+
)?;
395+
396+
// context before insertion
397+
diff_disp.write_line(
398+
self.file2,
399+
diff_disp.curr_pos2,
400+
hunk.ln2_start(),
401+
" ",
402+
false,
403+
)?;
404+
// add insert hunk
405+
let h2_prefix = if hunk.ln1_end() - hunk.ln1_start() > 0 {
378406
"! "
407+
} else {
408+
"+ "
379409
};
410+
diff_disp.write_line(
411+
self.file2,
412+
hunk.ln2_start(),
413+
hunk.ln2_end(),
414+
h2_prefix,
415+
false,
416+
)?;
417+
}
380418

381-
// dont print context for empty hunk
382-
if hunk.ln2_start() == hunk.ln2_end() {
383-
continue;
419+
// print final hunk
420+
if !diff_disp.hunk_lines.is_empty() {
421+
// display the remaining context if possible
422+
if diff_disp.curr_pos1 < self.file1.lines().len() {
423+
let end1 = self.file1.lines().len().min(diff_disp.curr_pos1 + context);
424+
let end2 = self.file2.lines().len().min(diff_disp.curr_pos2 + context);
425+
diff_disp.write_line(self.file1, diff_disp.curr_pos1, end1, " ", true)?;
426+
diff_disp.write_line(self.file2, diff_disp.curr_pos2, end2, " ", false)?;
384427
}
428+
let print_lines1 = diff_disp.hunk_lines[0].split('\n').any(|x| x.starts_with('-') || x.starts_with('!'));
429+
diff_disp.print_section(true, print_lines1);
430+
let print_lines2 = diff_disp.hunk_lines[1].split('\n').any(|x| x.starts_with('+') || x.starts_with('!'));
431+
diff_disp.print_section(false, print_lines2);
432+
}
385433

386-
for i in hunk_start..hunk_start + hunk_len {
387-
if i < hunk.ln2_start() {
388-
println!(" {}", self.file2.line(i));
389-
} else if i < hunk.ln2_end() {
390-
println!("{h2_prefix}{}", self.file2.line(i));
391-
} else {
392-
println!(" {}", self.file2.line(i));
393-
}
394-
}
434+
if !self.file1.ends_with_newline() {
435+
println!("{}", NO_NEW_LINE_AT_END_OF_FILE);
395436
}
437+
if !self.file2.ends_with_newline() {
438+
println!("{}", NO_NEW_LINE_AT_END_OF_FILE);
439+
}
440+
Ok(())
396441
}
397442

398443
fn print_unified(&mut self, unified: usize) -> Result<(), std::fmt::Error> {
@@ -405,7 +450,7 @@ impl<'a> FileDiff<'a> {
405450
Self::get_header(self.file2, self.format_options.label2())
406451
);
407452

408-
let mut diff_disp = DiffDisplay::new();
453+
let mut diff_disp = UnifiedDiffDisplay::new();
409454

410455
for hunk in self.hunks.hunks() {
411456
// move cursor to the start of context for first hunk
@@ -473,7 +518,7 @@ impl<'a> FileDiff<'a> {
473518
}
474519
}
475520

476-
pub struct DiffDisplay {
521+
pub struct UnifiedDiffDisplay {
477522
curr_pos1: usize,
478523
curr_pos2: usize,
479524
//`context_start` and `hunk_len` to get the values for the hunk header
@@ -486,7 +531,7 @@ pub struct DiffDisplay {
486531
hunk_lines: String,
487532
}
488533

489-
impl DiffDisplay {
534+
impl UnifiedDiffDisplay {
490535
pub fn new() -> Self {
491536
Self {
492537
curr_pos1: 0,
@@ -553,3 +598,116 @@ impl DiffDisplay {
553598
self.hunk_lines.clear();
554599
}
555600
}
601+
602+
pub struct ContextDiffDisplay {
603+
curr_pos1: usize,
604+
curr_pos2: usize,
605+
//`context_start` and `hunk_len` to get the values for the hunk header
606+
//keep track of the lines where context start
607+
context_start1: usize,
608+
context_start2: usize,
609+
//keep track of the length of the current hunk
610+
hunk1_len: usize,
611+
hunk2_len: usize,
612+
hunk_lines: [String; 2],
613+
}
614+
615+
impl ContextDiffDisplay {
616+
pub fn new() -> Self {
617+
Self {
618+
curr_pos1: 0,
619+
curr_pos2: 0,
620+
context_start1: 0,
621+
context_start2: 0,
622+
hunk1_len: 0,
623+
hunk2_len: 0,
624+
hunk_lines: [String::new(), String::new()],
625+
}
626+
}
627+
628+
pub fn set_context_start(&mut self) {
629+
self.context_start1 = self.curr_pos1 + 1;
630+
self.context_start2 = self.curr_pos2 + 1;
631+
}
632+
pub fn update_curr_pos(&mut self, curr1: usize, curr2: usize) {
633+
self.curr_pos1 = curr1;
634+
self.curr_pos2 = curr2;
635+
}
636+
637+
pub fn write_line(
638+
&mut self,
639+
file: &FileData,
640+
start: usize,
641+
end: usize,
642+
prefix: &str,
643+
is_file1: bool,
644+
) -> Result<(), std::fmt::Error> {
645+
let mut offset = 0;
646+
let file_index = if is_file1 { 0 } else { 1 };
647+
648+
for i in start..end {
649+
writeln!(self.hunk_lines[file_index], "{prefix}{}", file.line(i))?;
650+
offset += 1;
651+
}
652+
if prefix.starts_with(" ") {
653+
if is_file1 {
654+
self.curr_pos1 += offset;
655+
self.hunk1_len += offset;
656+
} else {
657+
self.curr_pos2 += offset;
658+
self.hunk2_len += offset;
659+
}
660+
} else if prefix.starts_with("-") {
661+
self.curr_pos1 += offset;
662+
self.hunk1_len += offset;
663+
} else if prefix.starts_with("+") {
664+
self.curr_pos2 += offset;
665+
self.hunk2_len += offset;
666+
} else if prefix.starts_with("!") {
667+
if is_file1 {
668+
self.curr_pos1 += offset;
669+
self.hunk1_len += offset;
670+
} else {
671+
self.curr_pos2 += offset;
672+
self.hunk2_len += offset;
673+
}
674+
}
675+
Ok(())
676+
}
677+
678+
pub fn print_section(&mut self, is_file1: bool, print_lines: bool) {
679+
if is_file1 {
680+
println!(
681+
"***************\n*** {},{} ****",
682+
self.context_start1,
683+
self.context_start1 + self.hunk1_len - 1
684+
);
685+
if print_lines {
686+
self.print_hunk(true);
687+
}
688+
self.context_start1 = self.curr_pos1 + 1;
689+
self.hunk1_len = 0;
690+
} else {
691+
println!(
692+
"--- {},{} ----",
693+
self.context_start2,
694+
self.context_start2 + self.hunk2_len - 1
695+
);
696+
if print_lines {
697+
self.print_hunk(false);
698+
}
699+
self.context_start2 = self.curr_pos2 + 1;
700+
self.hunk2_len = 0;
701+
}
702+
}
703+
704+
pub fn print_hunk(&mut self, is_file1: bool) {
705+
let file_index = if is_file1 { 0 } else { 1 };
706+
let lines = &mut self.hunk_lines[file_index];
707+
if lines.ends_with('\n') {
708+
lines.pop();
709+
}
710+
println!("{}", lines);
711+
self.hunk_lines[file_index].clear();
712+
}
713+
}

0 commit comments

Comments
 (0)