@@ -10,6 +10,7 @@ use super::{
1010use crate :: diff_util:: constants:: NO_NEW_LINE_AT_END_OF_FILE ;
1111
1212use 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