@@ -20,9 +20,15 @@ final class UnifiedDiffOutputBuilder extends AbstractChunkOutputBuilder
2020 */
2121 private $ header ;
2222
23- public function __construct (string $ header = "--- Original \n+++ New \n" )
23+ /**
24+ * @var bool
25+ */
26+ private $ addLineNumbers ;
27+
28+ public function __construct (string $ header = "--- Original \n+++ New \n" , bool $ addLineNumbers = false )
2429 {
25- $ this ->header = $ header ;
30+ $ this ->header = $ header ;
31+ $ this ->addLineNumbers = $ addLineNumbers ;
2632 }
2733
2834 public function getDiff (array $ diff ): string
@@ -49,71 +55,111 @@ public function getDiff(array $diff): string
4955 // of a list of elements all containing `same` (0) entries.
5056 private function writeDiffChunked ($ output , array $ diff , array $ old )
5157 {
52- $ start = isset ($ old [0 ]) ? $ old [0 ] : 0 ;
53- $ end = \count ($ diff );
58+ $ upperLimit = \count ($ diff );
59+ $ start = 0 ;
60+ $ fromStart = 0 ;
61+ $ toStart = 0 ;
62+
63+ if (\count ($ old )) { // no common parts, list all diff entries
64+ \reset ($ old );
65+
66+ // iterate the diff, go from chunk to chunk skipping common chunk of lines between those
67+ do {
68+ $ commonStart = \key ($ old );
69+ $ commonEnd = \current ($ old );
70+
71+ if ($ commonStart !== $ start ) {
72+ list ($ fromRange , $ toRange ) = $ this ->getChunkRange ($ diff , $ start , $ commonStart );
73+ $ this ->writeChunk ($ output , $ diff , $ start , $ commonStart , $ fromStart , $ fromRange , $ toStart , $ toRange );
5474
55- if (\count ($ old )) {
56- \end ($ old );
75+ $ fromStart += $ fromRange ;
76+ $ toStart += $ toRange ;
77+ }
78+
79+ $ start = $ commonEnd + 1 ;
80+ $ commonLength = $ commonEnd - $ commonStart + 1 ; // calculate number of non-change lines in the common part
81+ $ fromStart += $ commonLength ;
82+ $ toStart += $ commonLength ;
83+ } while (false !== \next ($ old ));
84+
85+ \end ($ old ); // short cut for finding possible last `change entry`
5786 $ tmp = \key ($ old );
5887 \reset ($ old );
59- if ($ old [$ tmp ] === $ end - 1 ) {
60- $ end = $ tmp ;
88+ if ($ old [$ tmp ] === $ upperLimit - 1 ) {
89+ $ upperLimit = $ tmp ;
6190 }
6291 }
6392
64- if (!isset ($ old [$ start ])) {
65- $ this ->writeDiffBufferElementNew ($ diff , $ output , $ start );
66- ++$ start ;
93+ if ($ start < $ upperLimit - 1 ) { // check for trailing (non) diff entries
94+ do {
95+ --$ upperLimit ;
96+ } while (isset ($ diff [$ upperLimit ][1 ]) && $ diff [$ upperLimit ][1 ] === 0 );
97+ ++$ upperLimit ;
98+
99+ list ($ fromRange , $ toRange ) = $ this ->getChunkRange ($ diff , $ start , $ upperLimit );
100+ $ this ->writeChunk ($ output , $ diff , $ start , $ upperLimit , $ fromStart , $ fromRange , $ toStart , $ toRange );
67101 }
102+ }
103+
104+ private function writeChunk (
105+ $ output ,
106+ array $ diff ,
107+ int $ diffStartIndex ,
108+ int $ diffEndIndex ,
109+ int $ fromStart ,
110+ int $ fromRange ,
111+ int $ toStart ,
112+ int $ toRange
113+ ) {
114+ if ($ this ->addLineNumbers ) {
115+ \fwrite ($ output , '@@ - ' . (1 + $ fromStart ));
116+
117+ if ($ fromRange > 1 ) {
118+ \fwrite ($ output , ', ' . $ fromRange );
119+ }
68120
69- for ($ i = $ start ; $ i < $ end ; $ i ++) {
70- if (isset ($ old [$ i ])) {
71- $ i = $ old [$ i ];
72- $ this ->writeDiffBufferElementNew ($ diff , $ output , $ i );
73- } else {
74- $ this ->writeDiffBufferElement ($ diff , $ output , $ i );
121+ \fwrite ($ output , ' + ' . (1 + $ toStart ));
122+ if ($ toRange > 1 ) {
123+ \fwrite ($ output , ', ' . $ toRange );
75124 }
125+
126+ \fwrite ($ output , " @@ \n" );
127+ } else {
128+ \fwrite ($ output , "@@ @@ \n" );
76129 }
77- }
78130
79- /**
80- * Gets individual buffer element with opening.
81- *
82- * @param array $diff
83- * @param resource $buffer
84- * @param int $diffIndex
85- */
86- private function writeDiffBufferElementNew (array $ diff , $ buffer , int $ diffIndex )
87- {
88- \fwrite ($ buffer , "@@ @@ \n" );
131+ for ($ i = $ diffStartIndex ; $ i < $ diffEndIndex ; ++$ i ) {
132+ if ($ diff [$ i ][1 ] === 1 /* ADDED */ ) {
133+ \fwrite ($ output , '+ ' . $ diff [$ i ][0 ]);
134+ } elseif ($ diff [$ i ][1 ] === 2 /* REMOVED */ ) {
135+ \fwrite ($ output , '- ' . $ diff [$ i ][0 ]);
136+ } else { /* Not changed (old) 0 or Warning 3 */
137+ \fwrite ($ output , ' ' . $ diff [$ i ][0 ]);
138+ }
89139
90- $ this ->writeDiffBufferElement ($ diff , $ buffer , $ diffIndex );
140+ $ lc = \substr ($ diff [$ i ][0 ], -1 );
141+ if ($ lc !== "\n" && $ lc !== "\r" ) {
142+ \fwrite ($ output , "\n" ); // \No newline at end of file
143+ }
144+ }
91145 }
92146
93- /**
94- * Gets individual buffer element.
95- *
96- * @param array $diff
97- * @param resource $buffer
98- * @param int $diffIndex
99- */
100- private function writeDiffBufferElement (array $ diff , $ buffer , int $ diffIndex )
147+ private function getChunkRange (array $ diff , int $ diffStartIndex , int $ diffEndIndex ): array
101148 {
102- if ($ diff [$ diffIndex ][1 ] === 1 /* ADDED */ ) {
103- \fwrite ($ buffer , '+ ' . $ diff [$ diffIndex ][0 ]);
104- } elseif ($ diff [$ diffIndex ][1 ] === 2 /* REMOVED */ ) {
105- \fwrite ($ buffer , '- ' . $ diff [$ diffIndex ][0 ]);
106- } elseif ($ diff [$ diffIndex ][1 ] === 3 /* WARNING */ ) {
107- \fwrite ($ buffer , ' ' . $ diff [$ diffIndex ][0 ]);
108-
109- return ; // Warnings should not be tested for line break, it will always be there
110- } else { /* OLD Not changed (0) */
111- \fwrite ($ buffer , ' ' . $ diff [$ diffIndex ][0 ]);
149+ $ toRange = 0 ;
150+ $ fromRange = 0 ;
151+
152+ for ($ i = $ diffStartIndex ; $ i < $ diffEndIndex ; ++$ i ) {
153+ if ($ diff [$ i ][1 ] === 1 ) { // added
154+ ++$ toRange ;
155+ } elseif ($ diff [$ i ][1 ] === 2 ) { // removed
156+ ++$ fromRange ;
157+ } elseif ($ diff [$ i ][1 ] === 0 ) { // same
158+ ++$ fromRange ;
159+ ++$ toRange ;
160+ }
112161 }
113162
114- $ lc = \substr ($ diff [$ diffIndex ][0 ], -1 );
115- if ($ lc !== "\n" && $ lc !== "\r" ) {
116- \fwrite ($ buffer , "\n" ); // \No newline at end of file
117- }
163+ return [$ fromRange , $ toRange ];
118164 }
119165}
0 commit comments