public function testGenerateUTF8IntralineDiff() { // Both Strings Empty. $left = ''; $right = ''; $result = array(array(array(0, 0)), array(array(0, 0))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // Left String Empty. $left = ''; $right = "Grumpy☃at"; $result = array(array(array(0, 0)), array(array(0, 11))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // Right String Empty. $left = "Grumpy☃at"; $right = ''; $result = array(array(array(0, 11)), array(array(0, 0))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // Both Strings Same $left = "Grumpy☃at"; $right = "Grumpy☃at"; $result = array(array(array(0, 11)), array(array(0, 11))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // Both Strings are different. $left = "Grumpy☃at"; $right = 'Smiling Dog'; $result = array(array(array(1, 11)), array(array(1, 11))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // String with one difference in the middle. $left = 'GrumpyCat'; $right = "Grumpy☃at"; $result = array(array(array(0, 6), array(1, 1), array(0, 2)), array(array(0, 6), array(1, 3), array(0, 2))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // Differences in middle, not connected to each other. $left = 'GrumpyCat'; $right = "Grumpy☃a☃t"; $result = array(array(array(0, 6), array(1, 2), array(0, 1)), array(array(0, 6), array(1, 7), array(0, 1))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // String with difference at the beginning. $left = "GrumpyC☃t"; $right = "DrumpyC☃t"; $result = array(array(array(1, 1), array(0, 10)), array(array(1, 1), array(0, 10))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // String with difference at the end. $left = "GrumpyC☃t"; $right = "GrumpyC☃P"; $result = array(array(array(0, 10), array(1, 1)), array(array(0, 10), array(1, 1))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // String with differences at the beginning and end. $left = "GrumpyC☃t"; $right = "DrumpyC☃P"; $result = array(array(array(1, 1), array(0, 9), array(1, 1)), array(array(1, 1), array(0, 9), array(1, 1))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); // This is a unicode combining character, "COMBINING DOUBLE TILDE". $cc = "͠"; $left = 'Senor'; $right = "Sen{$cc}or"; $result = array(array(array(0, 2), array(1, 1), array(0, 2)), array(array(0, 2), array(1, 3), array(0, 2))); $this->assertEqual($result, ArcanistDiffUtils::generateIntralineDiff($left, $right)); }
public function process() { $old = array(); $new = array(); $n = 0; $this->old = array_reverse($this->old); $this->new = array_reverse($this->new); $whitelines = false; $changed = false; $skip_intra = array(); while (count($this->old) || count($this->new)) { $o_desc = array_pop($this->old); $n_desc = array_pop($this->new); $oend = end($this->old); if ($oend) { $o_next = $oend['type']; } else { $o_next = null; } $nend = end($this->new); if ($nend) { $n_next = $nend['type']; } else { $n_next = null; } if ($o_desc) { $o_type = $o_desc['type']; } else { $o_type = null; } if ($n_desc) { $n_type = $n_desc['type']; } else { $n_type = null; } if ($o_type != null && $n_type == null) { $old[] = $o_desc; $new[] = null; if ($n_desc) { array_push($this->new, $n_desc); } $changed = true; continue; } if ($n_type != null && $o_type == null) { $old[] = null; $new[] = $n_desc; if ($o_desc) { array_push($this->old, $o_desc); } $changed = true; continue; } if ($this->whitespaceMode != self::WHITESPACE_SHOW_ALL) { $similar = false; switch ($this->whitespaceMode) { case self::WHITESPACE_IGNORE_TRAILING: if (rtrim($o_desc['text']) == rtrim($n_desc['text'])) { if ($o_desc['type']) { // If we're converting this into an unchanged line because of // a trailing whitespace difference, mark it as a whitespace // change so we can show "This file was modified only by // adding or removing trailing whitespace." instead of // "This file was not modified.". $whitelines = true; } $similar = true; } break; default: // In this case, the lines are similar if there is no change type // (that is, just trust the diff algorithm). if (!$o_desc['type']) { $similar = true; } break; } if ($similar) { $o_desc['type'] = null; $n_desc['type'] = null; $skip_intra[count($old)] = true; } else { $changed = true; } } else { $changed = true; } $old[] = $o_desc; $new[] = $n_desc; } $this->old = $old; $this->new = $new; $unchanged = false; if ($this->subparser) { $unchanged = $this->subparser->isUnchanged(); $whitelines = $this->subparser->isWhitespaceOnly(); } else { if (!$changed) { $filetype = $this->changeset->getFileType(); if ($filetype == DifferentialChangeType::FILE_TEXT || $filetype == DifferentialChangeType::FILE_SYMLINK) { $unchanged = true; } } } $this->specialAttributes = array(self::ATTR_UNCHANGED => $unchanged, self::ATTR_DELETED => array_filter($this->old) && !array_filter($this->new), self::ATTR_WHITELINES => $whitelines); if ($this->isSubparser) { // The rest of this function deals with formatting the diff for display; // we can exit early if we're a subparser and avoid doing extra work. return; } if ($this->subparser) { // Use this parser's side-by-side line information -- notably, the // change types -- but replace all the line text with the subparser's. // This lets us render whitespace-only changes without marking them as // different. $old = $this->old; $new = $this->new; $old_text = ipull($this->subparser->old, 'text', 'line'); $new_text = ipull($this->subparser->new, 'text', 'line'); foreach ($old as $k => $desc) { if (empty($desc)) { continue; } $old[$k]['text'] = idx($old_text, $desc['line']); } foreach ($new as $k => $desc) { if (empty($desc)) { continue; } $new[$k]['text'] = idx($new_text, $desc['line']); // If there's a corresponding "old" text and the line is marked as // unchanged, test if there are internal whitespace changes between // non-whitespace characters, e.g. spaces added to a string or spaces // added around operators. If we find internal spaces, mark the line // as changed. // // We only need to do this for "new" lines because any line that is // missing either "old" or "new" text certainly can not have internal // whitespace changes without also having non-whitespace changes, // because characters had to be either added or removed to create the // possibility of internal whitespace. if (isset($old[$k]['text']) && empty($new[$k]['type'])) { if (trim($old[$k]['text']) != trim($new[$k]['text'])) { // The strings aren't the same when trimmed, so there are internal // whitespace changes. Mark this line changed. $old[$k]['type'] = '-'; $new[$k]['type'] = '+'; } } } $this->old = $old; $this->new = $new; } $min_length = min(count($this->old), count($this->new)); for ($ii = 0; $ii < $min_length; $ii++) { if ($this->old[$ii] || $this->new[$ii]) { if (isset($this->old[$ii]['text'])) { $otext = $this->old[$ii]['text']; } else { $otext = ''; } if (isset($this->new[$ii]['text'])) { $ntext = $this->new[$ii]['text']; } else { $ntext = ''; } if ($otext != $ntext && empty($skip_intra[$ii])) { $this->intra[$ii] = ArcanistDiffUtils::generateIntralineDiff($otext, $ntext); } } } $lines_context = self::LINES_CONTEXT; $max_length = max(count($this->old), count($this->new)); $old = $this->old; $new = $this->new; $visible = false; $last = 0; for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) { $offset = $cursor + $lines_context; if (isset($old[$offset]) && $old[$offset]['type'] || isset($new[$offset]) && $new[$offset]['type']) { $visible = true; $last = $offset; } else { if ($cursor > $last + $lines_context) { $visible = false; } } if ($visible && $cursor > 0) { $this->visible[$cursor] = 1; } } // NOTE: Micro-optimize a couple of ipull()s here since it gives us a // 10% performance improvement for certain types of large diffs like // Phriction changes. $old_corpus = array(); foreach ($this->old as $o) { $old_corpus[] = $o['text']; } $old_corpus_block = implode("\n", $old_corpus); $new_corpus = array(); foreach ($this->new as $n) { $new_corpus[] = $n['text']; } $new_corpus_block = implode("\n", $new_corpus); $old_future = $this->getHighlightFuture($old_corpus_block); $new_future = $this->getHighlightFuture($new_corpus_block); $futures = array('old' => $old_future, 'new' => $new_future); foreach (Futures($futures) as $key => $future) { try { switch ($key) { case 'old': $this->oldRender = $this->processHighlightedSource($this->old, $future->resolve()); break; case 'new': $this->newRender = $this->processHighlightedSource($this->new, $future->resolve()); break; } } catch (Exception $ex) { phlog($ex); throw $ex; } } $this->applyIntraline($this->oldRender, ipull($this->intra, 0), $old_corpus); $this->applyIntraline($this->newRender, ipull($this->intra, 1), $new_corpus); $generated = strpos($new_corpus_block, '@' . 'generated') !== false; $this->specialAttributes[self::ATTR_GENERATED] = $generated; }
public function generateIntraLineDiffs() { $old = $this->getOldLines(); $new = $this->getNewLines(); $diffs = array(); foreach ($old as $key => $o) { $n = $new[$key]; if (!$o || !$n) { continue; } if ($o['type'] != $n['type']) { $diffs[$key] = ArcanistDiffUtils::generateIntralineDiff($o['text'], $n['text']); } } $this->setIntraLineDiffs($diffs); return $this; }