src/Diff.php
public function cleanupSemanticLossless()
    {
        $diffs = $this->getChanges();

        $pointer = 1;
        // Intentionally ignore the first and last element (don't need checking).
        while ($pointer < count($diffs) - 1) {
            if ($diffs[$pointer - 1][0] == self::EQUAL && $diffs[$pointer + 1][0] == self::EQUAL) {
                // This is a single edit surrounded by equalities.
                $equality1 = $diffs[$pointer - 1][1];
                $edit = $diffs[$pointer][1];
                $equality2 = $diffs[$pointer + 1][1];

                // First, shift the edit as far left as possible.
                $commonOffset = $this->getToolkit()->commonSuffix($equality1, $edit);
                if ($commonOffset) {
                    $commonString = mb_substr($edit, -$commonOffset);
                    $equality1 = mb_substr($equality1, 0, -$commonOffset);
                    $edit = $commonString . mb_substr($edit, 0, -$commonOffset);
                    $equality2 = $commonString . $equality2;
                }

                // Second, step character by character right, looking for the best fit.
                $bestEquality1 = $equality1;
                $bestEdit = $edit;
                $bestEquality2 = $equality2;
                $bestScore = $this->cleanupSemanticScore($equality1, $edit) + $this->cleanupSemanticScore($edit, $equality2);
                while ($edit && $equality2 && mb_substr($edit, 0, 1) == mb_substr($equality2, 0, 1)) {
                    $equality1 .= mb_substr($edit, 0, 1);
                    $edit = mb_substr($edit, 1) . mb_substr($equality2, 0, 1);
                    $equality2 = mb_substr($equality2, 1);
                    $score = $this->cleanupSemanticScore($equality1, $edit) + $this->cleanupSemanticScore($edit, $equality2);
                    // The >= encourages trailing rather than leading whitespace on edits.
                    if ($score >= $bestScore) {
                        $bestScore = $score;
                        $bestEquality1 = $equality1;
                        $bestEdit = $edit;
                        $bestEquality2 = $equality2;
                    }
                }
                if ($diffs[$pointer - 1][1] != $bestEquality1) {
                    // We have an improvement, save it back to the diff.
                    if ($bestEquality1 != '') {
                        $diffs[$pointer - 1][1] = $bestEquality1;
                    } else {
                        array_splice($diffs, $pointer - 1, 1);
                        $pointer -= 1;
                    }
                    $diffs[$pointer][1] = $bestEdit;
                    if ($bestEquality2 != '') {
                        $diffs[$pointer + 1][1] = $bestEquality2;
                    } else {
                        array_splice($diffs, $pointer + 1, 1);
                        $pointer -= 1;
                    }
                }
            }
            $pointer++;
        }
        $this->setChanges($diffs);

        return $this;
    }
public function cleanupSemanticLossless()
    {
        $diffs = $this->getChanges();

        $pointer = 1;
        // Intentionally ignore the first and last element (don't need checking).
        while ($pointer < count($diffs) - 1) {
            if ($diffs[$pointer - 1][0] == self::EQUAL && $diffs[$pointer + 1][0] == self::EQUAL) {
                // This is a single edit surrounded by equalities.
                $equality1 = $diffs[$pointer - 1][1];
                $edit = $diffs[$pointer][1];
                $equality2 = $diffs[$pointer + 1][1];

                // First, shift the edit as far left as possible.
                $commonOffset = $this->getToolkit()->commonSuffix($equality1, $edit);
                if ($commonOffset) {
                    $commonString = mb_substr($edit, -$commonOffset);
                    $equality1 = mb_substr($equality1, 0, -$commonOffset);
                    $edit = $commonString . mb_substr($edit, 0, -$commonOffset);
                    $equality2 = $commonString . $equality2;
                }

                // Second, step character by character right, looking for the best fit.
                $bestEquality1 = $equality1;
                $bestEdit = $edit;
                $bestEquality2 = $equality2;
                $bestScore = $this->cleanupSemanticScore($equality1, $edit) + $this->cleanupSemanticScore($edit, $equality2);
                while ($edit && $equality2 && mb_substr($edit, 0, 1) == mb_substr($equality2, 0, 1)) {
                    $equality1 .= mb_substr($edit, 0, 1);
                    $edit = mb_substr($edit, 1) . mb_substr($equality2, 0, 1);
                    $equality2 = mb_substr($equality2, 1);
                    $score = $this->cleanupSemanticScore($equality1, $edit) + $this->cleanupSemanticScore($edit, $equality2);
                    // The >= encourages trailing rather than leading whitespace on edits.
                    if ($score >= $bestScore) {
                        $bestScore = $score;
                        $bestEquality1 = $equality1;
                        $bestEdit = $edit;
                        $bestEquality2 = $equality2;
                    }
                }
                if ($diffs[$pointer - 1][1] != $bestEquality1) {
                    // We have an improvement, save it back to the diff.
                    if ($bestEquality1 != '') {
                        $diffs[$pointer - 1][1] = $bestEquality1;
                    } else {
                        array_splice($diffs, $pointer - 1, 1);
                        $pointer -= 1;
                    }
                    $diffs[$pointer][1] = $bestEdit;
                    if ($bestEquality2 != '') {
                        $diffs[$pointer + 1][1] = $bestEquality2;
                    } else {
                        array_splice($diffs, $pointer + 1, 1);
                        $pointer -= 1;
                    }
                }
            }
            $pointer++;
        }
        $this->setChanges($diffs);

        return $this;
    }
Look for single edits surrounded on both sides by equalities
which can be shifted sideways to align the edit to a word boundary.
e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.

@return $this