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;
 }