@@ -242,14 +242,6 @@ protected function mergeReplaceLines(string $oldLine, string $newLine): string
242242 SequenceMatcher::OP_INS
243243 );
244244
245- // create a sorted merged parts array
246- $ mergedParts = \array_merge ($ delParts , $ insParts );
247- \usort ($ mergedParts , function (array $ a , array $ b ): int {
248- // first sort by "offsetClean" then by "type"
249- return $ a ['offsetClean ' ] <=> $ b ['offsetClean ' ]
250- ?: ($ a ['type ' ] === SequenceMatcher::OP_DEL ? -1 : 1 );
251- });
252-
253245 // get the cleaned line by a non-regex way (should be faster)
254246 // i.e., the new line with all "<ins>...</ins>" parts removed
255247 $ mergedLine = $ newLine ;
@@ -262,16 +254,24 @@ protected function mergeReplaceLines(string $oldLine, string $newLine): string
262254 );
263255 }
264256
257+ // before building the $mergedParts, we do some adjustments
258+ $ this ->revisePartsForBoundaryNewlines ($ delParts , RendererConstant::HTML_CLOSURES_DEL );
259+ $ this ->revisePartsForBoundaryNewlines ($ insParts , RendererConstant::HTML_CLOSURES_INS );
260+
261+ // create a sorted merged parts array
262+ $ mergedParts = \array_merge ($ delParts , $ insParts );
263+ \usort ($ mergedParts , function (array $ a , array $ b ): int {
264+ // first sort by "offsetClean", "order" then by "type"
265+ return $ a ['offsetClean ' ] <=> $ b ['offsetClean ' ]
266+ ?: $ a ['order ' ] <=> $ b ['order ' ]
267+ ?: ($ a ['type ' ] === SequenceMatcher::OP_DEL ? -1 : 1 );
268+ });
269+
265270 // insert merged parts into the cleaned line
266271 foreach (ReverseIterator::fromArray ($ mergedParts ) as $ part ) {
267272 $ mergedLine = \substr_replace (
268273 $ mergedLine ,
269- // the closure part from its source string
270- \substr (
271- $ part ['type ' ] === SequenceMatcher::OP_DEL ? $ oldLine : $ newLine ,
272- $ part ['offset ' ],
273- $ part ['length ' ]
274- ),
274+ $ part ['content ' ],
275275 $ part ['offsetClean ' ],
276276 0 // insertion
277277 );
@@ -315,11 +315,15 @@ protected function analyzeClosureParts(string $ld, string $rd, string $line, int
315315
316316 $ parts [] = [
317317 'type ' => $ type ,
318+ // the sorting order used when both "offsetClean" are the same
319+ 'order ' => 0 ,
318320 // the offset in the line
319321 'offset ' => $ partStart ,
320322 'length ' => $ partLength ,
321323 // the offset in the cleaned line (i.e., the line with closure parts removed)
322324 'offsetClean ' => $ partStart - $ partLengthSum ,
325+ // the content of the part
326+ 'content ' => \substr ($ line , $ partStart , $ partLength ),
323327 ];
324328
325329 $ partLengthSum += $ partLength ;
@@ -393,4 +397,48 @@ protected function fixLinesForNoClosure(array &$lines, array $closures): void
393397 }
394398 }
395399 }
400+
401+ /**
402+ * Move boundary newline chars in parts to be extra new parts.
403+ *
404+ * @param array[] $parts the parts
405+ * @param string[] $closures the closures
406+ *
407+ * @see https://git.io/JvVXH
408+ */
409+ protected function revisePartsForBoundaryNewlines (array &$ parts , array $ closures ): void
410+ {
411+ $ closureRegexL = \preg_quote ($ closures [0 ], '/ ' );
412+ $ closureRegexR = \preg_quote ($ closures [1 ], '/ ' );
413+
414+ for ($ i = \count ($ parts ) - 1 ; $ i >= 0 ; --$ i ) {
415+ $ part = &$ parts [$ i ];
416+
417+ $ part ['content ' ] = \preg_replace_callback (
418+ "/(?P<closure> {$ closureRegexL })(?P<nl>[ \r\n]++)/u " ,
419+ function (array $ matches ) use (&$ parts , $ part , $ closures ): string {
420+ // add a new part for the extract newline chars
421+ $ part ['order ' ] = -1 ;
422+ $ part ['content ' ] = "{$ closures [0 ]}{$ matches ['nl ' ]}{$ closures [1 ]}" ;
423+ $ parts [] = $ part ;
424+
425+ return $ matches ['closure ' ];
426+ },
427+ $ part ['content ' ]
428+ );
429+
430+ $ part ['content ' ] = \preg_replace_callback (
431+ "/(?P<nl>[ \r\n]++)(?P<closure> {$ closureRegexR })/u " ,
432+ function (array $ matches ) use (&$ parts , $ part , $ closures ): string {
433+ // add a new part for the extract newline chars
434+ $ part ['order ' ] = 1 ;
435+ $ part ['content ' ] = "{$ closures [0 ]}{$ matches ['nl ' ]}{$ closures [1 ]}" ;
436+ $ parts [] = $ part ;
437+
438+ return $ matches ['closure ' ];
439+ },
440+ $ part ['content ' ]
441+ );
442+ }
443+ }
396444}
0 commit comments