コード例 #1
0
 private function renderGrepResults(array $results)
 {
     $drequest = $this->getDiffusionRequest();
     require_celerity_resource('syntax-highlighting-css');
     // NOTE: This can be wrong because we may find the string inside the
     // comment. But it's correct in most cases and highlighting the whole file
     // would be too expensive.
     $futures = array();
     $engine = PhabricatorSyntaxHighlighter::newEngine();
     foreach ($results as $result) {
         list($path, $line, $string) = $result;
         $futures["{$path}:{$line}"] = $engine->getHighlightFuture($engine->getLanguageFromFilename($path), ltrim($string));
     }
     try {
         Futures($futures)->limit(8)->resolveAll();
     } catch (PhutilSyntaxHighlighterException $ex) {
     }
     $rows = array();
     foreach ($results as $result) {
         list($path, $line, $string) = $result;
         $href = $drequest->generateURI(array('action' => 'browse', 'path' => $path, 'line' => $line));
         try {
             $string = $futures["{$path}:{$line}"]->resolve();
         } catch (PhutilSyntaxHighlighterException $ex) {
         }
         $string = phutil_tag('pre', array('class' => 'PhabricatorMonospaced'), $string);
         $path = Filesystem::readablePath($path, $drequest->getPath());
         $rows[] = array(phutil_tag('a', array('href' => $href), $path), $line, $string);
     }
     $table = id(new AphrontTableView($rows))->setClassName('remarkup-code')->setHeaders(array(pht('Path'), pht('Line'), pht('String')))->setColumnClasses(array('', 'n', 'wide'))->setNoDataString(pht('The pattern you searched for was not found in the content of any ' . 'files.'));
     return $table;
 }
コード例 #2
0
 private function buildSourceCodeView(PhabricatorPaste $paste, PhabricatorFile $file)
 {
     $language = $paste->getLanguage();
     $source = $file->loadFileData();
     if (empty($language)) {
         $source = PhabricatorSyntaxHighlighter::highlightWithFilename($paste->getTitle(), $source);
     } else {
         $source = PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source);
     }
     $lines = explode("\n", $source);
     return id(new PhabricatorSourceCodeView())->setLines($lines);
 }
コード例 #3
0
 private function buildCorpus($paste, $file)
 {
     // Blantently copied from DiffusionBrowseFileController
     require_celerity_resource('diffusion-source-css');
     require_celerity_resource('syntax-highlighting-css');
     $language = $paste->getLanguage();
     $source = $file->loadFileData();
     if (empty($language)) {
         $source = PhabricatorSyntaxHighlighter::highlightWithFilename($paste->getTitle(), $source);
     } else {
         $source = PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source);
     }
     $text_list = explode("\n", $source);
     $rows = $this->buildDisplayRows($text_list);
     $corpus_table = phutil_render_tag('table', array('class' => 'diffusion-source remarkup-code PhabricatorMonospaced'), implode("\n", $rows));
     $corpus = phutil_render_tag('div', array('style' => 'padding: 0pt 2em;'), $corpus_table);
     return $corpus;
 }
コード例 #4
0
 private function buildCorpus($paste, $file)
 {
     // Blantently copied from DiffusionBrowseFileController
     require_celerity_resource('diffusion-source-css');
     require_celerity_resource('syntax-highlighting-css');
     $language = $paste->getLanguage();
     $source = $file->loadFileData();
     if (empty($language)) {
         $source = PhabricatorSyntaxHighlighter::highlightWithFilename($paste->getTitle(), $source);
     } else {
         $source = PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source);
     }
     $text_list = explode("\n", $source);
     Javelin::initBehavior('phabricator-oncopy', array());
     $rows = $this->buildDisplayRows($text_list);
     // TODO: Split the "one-up source listing" view into its own class and
     // share it properly between Paste and Diffusion.
     $corpus_table = phutil_render_tag('table', array('class' => 'diffusion-source remarkup-code PhabricatorMonospaced'), implode("\n", $rows));
     $corpus = phutil_render_tag('div', array('style' => 'padding: 0pt 2em;'), $corpus_table);
     return $corpus;
 }
コード例 #5
0
 private function buildSymbolIndexes(PhabricatorRepositoryArcanistProject $arc_project, array $visible_changesets)
 {
     assert_instances_of($visible_changesets, 'DifferentialChangeset');
     $engine = PhabricatorSyntaxHighlighter::newEngine();
     $langs = $arc_project->getSymbolIndexLanguages();
     if (!$langs) {
         return array(array(), array());
     }
     $symbol_indexes = array();
     $project_phids = array_merge(array($arc_project->getPHID()), nonempty($arc_project->getSymbolIndexProjects(), array()));
     $indexed_langs = array_fill_keys($langs, true);
     foreach ($visible_changesets as $key => $changeset) {
         $lang = $engine->getLanguageFromFilename($changeset->getFilename());
         if (isset($indexed_langs[$lang])) {
             $symbol_indexes[$key] = array('lang' => $lang, 'projects' => $project_phids);
         }
     }
     return array($symbol_indexes, $project_phids);
 }
コード例 #6
0
 public function render($range_start = null, $range_len = null, $mask_force = array())
 {
     // "Top level" renders are initial requests for the whole file, versus
     // requests for a specific range generated by clicking "show more". We
     // generate property changes and "shield" UI elements only for toplevel
     // requests.
     $this->isTopLevel = $range_start === null && $range_len === null;
     $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
     $this->tryCacheStuff();
     $feedback_mask = array();
     switch ($this->changeset->getFileType()) {
         case DifferentialChangeType::FILE_IMAGE:
             $old = null;
             $cur = null;
             $metadata = $this->changeset->getMetadata();
             $data = idx($metadata, 'attachment-data');
             $old_phid = idx($metadata, 'old:binary-phid');
             $new_phid = idx($metadata, 'new:binary-phid');
             if ($old_phid || $new_phid) {
                 if ($old_phid) {
                     $old_uri = PhabricatorFileURI::getViewURIForPHID($old_phid);
                     $old = phutil_render_tag('img', array('src' => $old_uri));
                 }
                 if ($new_phid) {
                     $new_uri = PhabricatorFileURI::getViewURIForPHID($new_phid);
                     $cur = phutil_render_tag('img', array('src' => $new_uri));
                 }
             }
             $output = $this->renderChangesetTable($this->changeset, '<tr>' . '<th></th>' . '<td class="differential-old-image">' . '<div class="differential-image-stage">' . $old . '</div>' . '</td>' . '<th></th>' . '<td class="differential-new-image">' . '<div class="differential-image-stage">' . $cur . '</div>' . '</td>' . '</tr>');
             return $output;
         case DifferentialChangeType::FILE_DIRECTORY:
         case DifferentialChangeType::FILE_BINARY:
             $output = $this->renderChangesetTable($this->changeset, null);
             return $output;
     }
     $shield = null;
     if ($this->isTopLevel && !$this->comments) {
         if ($this->isGenerated()) {
             $shield = $this->renderShield("This file contains generated code, which does not normally need " . "to be reviewed.", true);
         } else {
             if ($this->isUnchanged()) {
                 if ($this->isWhitespaceOnly()) {
                     $shield = $this->renderShield("This file was changed only by adding or removing trailing " . "whitespace.", false);
                 } else {
                     $shield = $this->renderShield("The contents of this file were not changed.", false);
                 }
             } else {
                 if ($this->isDeleted()) {
                     $shield = $this->renderShield("This file was completely deleted.", true);
                 } else {
                     if ($this->changeset->getAffectedLineCount() > 2500) {
                         $lines = number_format($this->changeset->getAffectedLineCount());
                         $shield = $this->renderShield("This file has a very large number of changes ({$lines} lines).", true);
                     }
                 }
             }
         }
     }
     if ($shield) {
         return $this->renderChangesetTable($this->changeset, $shield);
     }
     $old_comments = array();
     $new_comments = array();
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
     if ($this->comments) {
         foreach ($this->comments as $comment) {
             $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
             $end = $comment->getLineNumber() + $comment->getLineLength() + self::LINES_CONTEXT;
             $new = $this->isCommentOnRightSideWhenDisplayed($comment);
             for ($ii = $start; $ii <= $end; $ii++) {
                 if ($new) {
                     $new_mask[$ii] = true;
                 } else {
                     $old_mask[$ii] = true;
                 }
             }
         }
         foreach ($this->old as $ii => $old) {
             if (isset($old['line']) && isset($old_mask[$old['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         foreach ($this->new as $ii => $new) {
             if (isset($new['line']) && isset($new_mask[$new['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         $this->comments = msort($this->comments, 'getID');
         foreach ($this->comments as $comment) {
             $final = $comment->getLineNumber() + $comment->getLineLength();
             if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
                 $new_comments[$final][] = $comment;
             } else {
                 $old_comments[$final][] = $comment;
             }
         }
     }
     $html = $this->renderTextChange($range_start, $range_len, $mask_force, $feedback_mask, $old_comments, $new_comments);
     return $this->renderChangesetTable($this->changeset, $html);
 }
コード例 #7
0
 private function buildCorpus($show_blame, $show_color, $file_corpus, $needs_blame, DiffusionRequest $drequest, $path, $data)
 {
     $viewer = $this->getViewer();
     $blame_timeout = 15;
     $blame_failed = false;
     $highlight_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;
     $blame_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;
     $can_highlight = strlen($file_corpus) <= $highlight_limit;
     $can_blame = strlen($file_corpus) <= $blame_limit;
     if ($needs_blame && $can_blame) {
         $blame = $this->loadBlame($path, $drequest->getCommit(), $blame_timeout);
         list($blame_list, $blame_commits) = $blame;
         if ($blame_list === null) {
             $blame_failed = true;
             $blame_list = array();
         }
     } else {
         $blame_list = array();
         $blame_commits = array();
     }
     if (!$show_color) {
         $corpus = $this->renderPlaintextCorpus($file_corpus, $blame_list, $blame_commits, $show_blame);
     } else {
         if ($can_highlight) {
             require_celerity_resource('syntax-highlighting-css');
             $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $file_corpus);
             $lines = phutil_split_lines($highlighted);
         } else {
             $lines = phutil_split_lines($file_corpus);
         }
         $rows = $this->buildDisplayRows($lines, $blame_list, $blame_commits, $show_blame, $show_color);
         $corpus_table = javelin_tag('table', array('class' => 'diffusion-source remarkup-code PhabricatorMonospaced', 'sigil' => 'phabricator-source'), $rows);
         if ($this->getRequest()->isAjax()) {
             return $corpus_table;
         }
         $id = celerity_generate_unique_node_id();
         $repo = $drequest->getRepository();
         $symbol_repos = nonempty($repo->getSymbolSources(), array());
         $symbol_repos[] = $repo->getPHID();
         $lang = last(explode('.', $drequest->getPath()));
         $repo_languages = $repo->getSymbolLanguages();
         $repo_languages = nonempty($repo_languages, array());
         $repo_languages = array_fill_keys($repo_languages, true);
         $needs_symbols = true;
         if ($repo_languages && $symbol_repos) {
             $have_symbols = id(new DiffusionSymbolQuery())->existsSymbolsInRepository($repo->getPHID());
             if (!$have_symbols) {
                 $needs_symbols = false;
             }
         }
         if ($needs_symbols && $repo_languages) {
             $needs_symbols = isset($repo_languages[$lang]);
         }
         if ($needs_symbols) {
             Javelin::initBehavior('repository-crossreference', array('container' => $id, 'lang' => $lang, 'repositories' => $symbol_repos));
         }
         $corpus = phutil_tag('div', array('id' => $id), $corpus_table);
         Javelin::initBehavior('load-blame', array('id' => $id));
     }
     $edit = $this->renderEditButton();
     $file = $this->renderFileButton();
     $header = id(new PHUIHeaderView())->setHeader(pht('File Contents'))->addActionLink($edit)->addActionLink($file);
     $corpus = id(new PHUIObjectBoxView())->setHeader($header)->appendChild($corpus)->setCollapsed(true);
     $messages = array();
     if (!$can_highlight) {
         $messages[] = pht('This file is larger than %s, so syntax highlighting is disabled ' . 'by default.', phutil_format_bytes($highlight_limit));
     }
     if ($show_blame && !$can_blame) {
         $messages[] = pht('This file is larger than %s, so blame is disabled.', phutil_format_bytes($blame_limit));
     }
     if ($blame_failed) {
         $messages[] = pht('Failed to load blame information for this file in %s second(s).', new PhutilNumber($blame_timeout));
     }
     if ($messages) {
         $corpus->setInfoView(id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors($messages));
     }
     return $corpus;
 }
コード例 #8
0
 public function render($range_start = null, $range_len = null, $mask_force = array())
 {
     // "Top level" renders are initial requests for the whole file, versus
     // requests for a specific range generated by clicking "show more". We
     // generate property changes and "shield" UI elements only for toplevel
     // requests.
     $this->isTopLevel = $range_start === null && $range_len === null;
     $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
     $this->tryCacheStuff();
     $shield = null;
     if ($this->isTopLevel && !$this->comments) {
         if ($this->isGenerated()) {
             $shield = $this->renderShield("This file contains generated code, which does not normally need " . "to be reviewed.", true);
         } else {
             if ($this->isUnchanged()) {
                 if ($this->isWhitespaceOnly()) {
                     $shield = $this->renderShield("This file was changed only by adding or removing trailing " . "whitespace.", false);
                 } else {
                     $shield = $this->renderShield("The contents of this file were not changed.", false);
                 }
             } else {
                 if ($this->isDeleted()) {
                     $shield = $this->renderShield("This file was completely deleted.", true);
                 } else {
                     if ($this->changeset->getAffectedLineCount() > 2500) {
                         $lines = number_format($this->changeset->getAffectedLineCount());
                         $shield = $this->renderShield("This file has a very large number of changes ({$lines} lines).", true);
                     }
                 }
             }
         }
     }
     if ($shield) {
         return $this->renderChangesetTable($this->changeset, $shield);
     }
     $feedback_mask = array();
     switch ($this->changeset->getFileType()) {
         case DifferentialChangeType::FILE_IMAGE:
             $old = null;
             $cur = null;
             // TODO: Improve the architectural issue as discussed in D955
             // https://secure.phabricator.com/D955
             $reference = $this->renderingReference;
             $parts = explode('/', $reference);
             if (count($parts) == 2) {
                 list($id, $vs) = $parts;
             } else {
                 $id = $parts[0];
                 $vs = 0;
             }
             $id = (int) $id;
             $vs = (int) $vs;
             if (!$vs) {
                 $metadata = $this->changeset->getMetadata();
                 $data = idx($metadata, 'attachment-data');
                 $old_phid = idx($metadata, 'old:binary-phid');
                 $new_phid = idx($metadata, 'new:binary-phid');
             } else {
                 $vs_changeset = id(new DifferentialChangeset())->load($vs);
                 $vs_metadata = $vs_changeset->getMetadata();
                 $old_phid = idx($vs_metadata, 'new:binary-phid');
                 $changeset = id(new DifferentialChangeset())->load($id);
                 $metadata = $changeset->getMetadata();
                 $new_phid = idx($metadata, 'new:binary-phid');
             }
             if ($old_phid || $new_phid) {
                 // grab the files, (micro) optimization for 1 query not 2
                 $file_phids = array();
                 if ($old_phid) {
                     $file_phids[] = $old_phid;
                 }
                 if ($new_phid) {
                     $file_phids[] = $new_phid;
                 }
                 $files = id(new PhabricatorFile())->loadAllWhere('phid IN (%Ls)', $file_phids);
                 foreach ($files as $file) {
                     if (empty($file)) {
                         continue;
                     }
                     if ($file->getPHID() == $old_phid) {
                         $old = phutil_render_tag('img', array('src' => $file->getBestURI()));
                     } else {
                         $cur = phutil_render_tag('img', array('src' => $file->getBestURI()));
                     }
                 }
             }
             $this->comments = msort($this->comments, 'getID');
             $old_comments = array();
             $new_comments = array();
             foreach ($this->comments as $comment) {
                 if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
                     $new_comments[] = $comment;
                 } else {
                     $old_comments[] = $comment;
                 }
             }
             $html_old = array();
             $html_new = array();
             foreach ($old_comments as $comment) {
                 $xhp = $this->renderInlineComment($comment);
                 $html_old[] = '<tr class="inline"><th /><td>' . $xhp . '</td><th /><td colspan="2" /></tr>';
             }
             foreach ($new_comments as $comment) {
                 $xhp = $this->renderInlineComment($comment);
                 $html_new[] = '<tr class="inline"><th /><td /><th /><td colspan="2">' . $xhp . '</td></tr>';
             }
             if (!$old) {
                 $th_old = '<th></th>';
             } else {
                 $th_old = '<th id="C' . $vs . 'OL1">1</th>';
             }
             if (!$cur) {
                 $th_new = '<th></th>';
             } else {
                 $th_new = '<th id="C' . $id . 'NL1">1</th>';
             }
             $output = $this->renderChangesetTable($this->changeset, '<tr class="differential-image-diff">' . $th_old . '<td class="differential-old-image">' . '<div class="differential-image-stage">' . $old . '</div>' . '</td>' . $th_new . '<td class="copy differential-new-image"></td>' . '<td class="differential-new-image">' . '<div class="differential-image-stage">' . $cur . '</div>' . '</td>' . '</tr>' . implode('', $html_old) . implode('', $html_new));
             return $output;
         case DifferentialChangeType::FILE_DIRECTORY:
         case DifferentialChangeType::FILE_BINARY:
             $output = $this->renderChangesetTable($this->changeset, null);
             return $output;
     }
     $old_comments = array();
     $new_comments = array();
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
     if ($this->comments) {
         foreach ($this->comments as $comment) {
             $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
             $end = $comment->getLineNumber() + $comment->getLineLength() + self::LINES_CONTEXT;
             $new = $this->isCommentOnRightSideWhenDisplayed($comment);
             for ($ii = $start; $ii <= $end; $ii++) {
                 if ($new) {
                     $new_mask[$ii] = true;
                 } else {
                     $old_mask[$ii] = true;
                 }
             }
         }
         foreach ($this->old as $ii => $old) {
             if (isset($old['line']) && isset($old_mask[$old['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         foreach ($this->new as $ii => $new) {
             if (isset($new['line']) && isset($new_mask[$new['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         $this->comments = msort($this->comments, 'getID');
         foreach ($this->comments as $comment) {
             $final = $comment->getLineNumber() + $comment->getLineLength();
             $final = max(1, $final);
             if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
                 $new_comments[$final][] = $comment;
             } else {
                 $old_comments[$final][] = $comment;
             }
         }
     }
     $html = $this->renderTextChange($range_start, $range_len, $mask_force, $feedback_mask, $old_comments, $new_comments);
     return $this->renderChangesetTable($this->changeset, $html);
 }
コード例 #9
0
 public function render($range_start = null, $range_len = null, $mask_force = array())
 {
     // "Top level" renders are initial requests for the whole file, versus
     // requests for a specific range generated by clicking "show more". We
     // generate property changes and "shield" UI elements only for toplevel
     // requests.
     $this->isTopLevel = $range_start === null && $range_len === null;
     $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
     $encoding = null;
     if ($this->characterEncoding) {
         // We are forcing this changeset to be interpreted with a specific
         // character encoding, so force all the hunks into that encoding and
         // propagate it to the renderer.
         $encoding = $this->characterEncoding;
         foreach ($this->changeset->getHunks() as $hunk) {
             $hunk->forceEncoding($this->characterEncoding);
         }
     } else {
         // We're just using the default, so tell the renderer what that is
         // (by reading the encoding from the first hunk).
         foreach ($this->changeset->getHunks() as $hunk) {
             $encoding = $hunk->getDataEncoding();
             break;
         }
     }
     $this->tryCacheStuff();
     // If we're rendering in an offset mode, treat the range numbers as line
     // numbers instead of rendering offsets.
     $offset_mode = $this->getOffsetMode();
     if ($offset_mode) {
         if ($offset_mode == 'new') {
             $offset_map = $this->new;
         } else {
             $offset_map = $this->old;
         }
         $range_end = $this->getOffset($offset_map, $range_start + $range_len);
         $range_start = $this->getOffset($offset_map, $range_start);
         $range_len = $range_end - $range_start;
     }
     $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
     $rows = max(count($this->old), count($this->new));
     $renderer = $this->getRenderer()->setUser($this->getUser())->setChangeset($this->changeset)->setRenderPropertyChangeHeader($render_pch)->setIsTopLevel($this->isTopLevel)->setOldRender($this->oldRender)->setNewRender($this->newRender)->setHunkStartLines($this->hunkStartLines)->setOldChangesetID($this->leftSideChangesetID)->setNewChangesetID($this->rightSideChangesetID)->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)->setCodeCoverage($this->getCoverage())->setRenderingReference($this->getRenderingReference())->setMarkupEngine($this->markupEngine)->setHandles($this->handles)->setOldLines($this->old)->setNewLines($this->new)->setOriginalCharacterEncoding($encoding)->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks())->setCanMarkDone($this->getCanMarkDone())->setObjectOwnerPHID($this->getObjectOwnerPHID())->setHighlightingDisabled($this->highlightingDisabled);
     $shield = null;
     if ($this->isTopLevel && !$this->comments) {
         if ($this->isGenerated()) {
             $shield = $renderer->renderShield(pht('This file contains generated code, which does not normally ' . 'need to be reviewed.'));
         } else {
             if ($this->isMoveAway()) {
                 // We put an empty shield on these files. Normally, they do not have
                 // any diff content anyway. However, if they come through `arc`, they
                 // may have content. We don't want to show it (it's not useful) and
                 // we bailed out of fully processing it earlier anyway.
                 // We could show a message like "this file was moved", but we show
                 // that as a change header anyway, so it would be redundant. Instead,
                 // just render an empty shield to skip rendering the diff body.
                 $shield = '';
             } else {
                 if ($this->isUnchanged()) {
                     $type = 'text';
                     if (!$rows) {
                         // NOTE: Normally, diffs which don't change files do not include
                         // file content (for example, if you "chmod +x" a file and then
                         // run "git show", the file content is not available). Similarly,
                         // if you move a file from A to B without changing it, diffs normally
                         // do not show the file content. In some cases `arc` is able to
                         // synthetically generate content for these diffs, but for raw diffs
                         // we'll never have it so we need to be prepared to not render a link.
                         $type = 'none';
                     }
                     $type_add = DifferentialChangeType::TYPE_ADD;
                     if ($this->changeset->getChangeType() == $type_add) {
                         // Although the generic message is sort of accurate in a technical
                         // sense, this more-tailored message is less confusing.
                         $shield = $renderer->renderShield(pht('This is an empty file.'), $type);
                     } else {
                         $shield = $renderer->renderShield(pht('The contents of this file were not changed.'), $type);
                     }
                 } else {
                     if ($this->isWhitespaceOnly()) {
                         $shield = $renderer->renderShield(pht('This file was changed only by adding or removing whitespace.'), 'whitespace');
                     } else {
                         if ($this->isDeleted()) {
                             $shield = $renderer->renderShield(pht('This file was completely deleted.'));
                         } else {
                             if ($this->changeset->getAffectedLineCount() > 2500) {
                                 $shield = $renderer->renderShield(pht('This file has a very large number of changes (%s lines).', new PhutilNumber($this->changeset->getAffectedLineCount())));
                             }
                         }
                     }
                 }
             }
         }
     }
     if ($shield !== null) {
         return $renderer->renderChangesetTable($shield);
     }
     // This request should render the "undershield" headers if it's a top-level
     // request which made it this far (indicating the changeset has no shield)
     // or it's a request with no mask information (indicating it's the request
     // that removes the rendering shield). Possibly, this second class of
     // request might need to be made more explicit.
     $is_undershield = empty($mask_force) || $this->isTopLevel;
     $renderer->setIsUndershield($is_undershield);
     $old_comments = array();
     $new_comments = array();
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
     $lines_context = $this->getLinesOfContext();
     if ($this->comments) {
         // If there are any comments which appear in sections of the file which
         // we don't have, we're going to move them backwards to the closest
         // earlier line. Two cases where this may happen are:
         //
         //   - Porting ghost comments forward into a file which was mostly
         //     deleted.
         //   - Porting ghost comments forward from a full-context diff to a
         //     partial-context diff.
         list($old_backmap, $new_backmap) = $this->buildLineBackmaps();
         foreach ($this->comments as $comment) {
             $new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
             $line = $comment->getLineNumber();
             if ($new_side) {
                 $back_line = $new_backmap[$line];
             } else {
                 $back_line = $old_backmap[$line];
             }
             if ($back_line != $line) {
                 // TODO: This should probably be cleaner, but just be simple and
                 // obvious for now.
                 $ghost = $comment->getIsGhost();
                 if ($ghost) {
                     $moved = pht('This comment originally appeared on line %s, but that line ' . 'does not exist in this version of the diff. It has been ' . 'moved backward to the nearest line.', new PhutilNumber($line));
                     $ghost['reason'] = $ghost['reason'] . "\n\n" . $moved;
                     $comment->setIsGhost($ghost);
                 }
                 $comment->setLineNumber($back_line);
                 $comment->setLineLength(0);
             }
             $start = max($comment->getLineNumber() - $lines_context, 0);
             $end = $comment->getLineNumber() + $comment->getLineLength() + $lines_context;
             for ($ii = $start; $ii <= $end; $ii++) {
                 if ($new_side) {
                     $new_mask[$ii] = true;
                 } else {
                     $old_mask[$ii] = true;
                 }
             }
         }
         foreach ($this->old as $ii => $old) {
             if (isset($old['line']) && isset($old_mask[$old['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         foreach ($this->new as $ii => $new) {
             if (isset($new['line']) && isset($new_mask[$new['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         $this->comments = $this->reorderAndThreadComments($this->comments);
         foreach ($this->comments as $comment) {
             $final = $comment->getLineNumber() + $comment->getLineLength();
             $final = max(1, $final);
             if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
                 $new_comments[$final][] = $comment;
             } else {
                 $old_comments[$final][] = $comment;
             }
         }
     }
     $renderer->setOldComments($old_comments)->setNewComments($new_comments);
     switch ($this->changeset->getFileType()) {
         case DifferentialChangeType::FILE_IMAGE:
             $old = null;
             $new = null;
             // TODO: Improve the architectural issue as discussed in D955
             // https://secure.phabricator.com/D955
             $reference = $this->getRenderingReference();
             $parts = explode('/', $reference);
             if (count($parts) == 2) {
                 list($id, $vs) = $parts;
             } else {
                 $id = $parts[0];
                 $vs = 0;
             }
             $id = (int) $id;
             $vs = (int) $vs;
             if (!$vs) {
                 $metadata = $this->changeset->getMetadata();
                 $data = idx($metadata, 'attachment-data');
                 $old_phid = idx($metadata, 'old:binary-phid');
                 $new_phid = idx($metadata, 'new:binary-phid');
             } else {
                 $vs_changeset = id(new DifferentialChangeset())->load($vs);
                 $old_phid = null;
                 $new_phid = null;
                 // TODO: This is spooky, see D6851
                 if ($vs_changeset) {
                     $vs_metadata = $vs_changeset->getMetadata();
                     $old_phid = idx($vs_metadata, 'new:binary-phid');
                 }
                 $changeset = id(new DifferentialChangeset())->load($id);
                 if ($changeset) {
                     $metadata = $changeset->getMetadata();
                     $new_phid = idx($metadata, 'new:binary-phid');
                 }
             }
             if ($old_phid || $new_phid) {
                 // grab the files, (micro) optimization for 1 query not 2
                 $file_phids = array();
                 if ($old_phid) {
                     $file_phids[] = $old_phid;
                 }
                 if ($new_phid) {
                     $file_phids[] = $new_phid;
                 }
                 $files = id(new PhabricatorFileQuery())->setViewer($this->getUser())->withPHIDs($file_phids)->execute();
                 foreach ($files as $file) {
                     if (empty($file)) {
                         continue;
                     }
                     if ($file->getPHID() == $old_phid) {
                         $old = $file;
                     } else {
                         if ($file->getPHID() == $new_phid) {
                             $new = $file;
                         }
                     }
                 }
             }
             $renderer->attachOldFile($old);
             $renderer->attachNewFile($new);
             return $renderer->renderFileChange($old, $new, $id, $vs);
         case DifferentialChangeType::FILE_DIRECTORY:
         case DifferentialChangeType::FILE_BINARY:
             $output = $renderer->renderChangesetTable(null);
             return $output;
     }
     if ($this->originalLeft && $this->originalRight) {
         list($highlight_old, $highlight_new) = $this->diffOriginals();
         $highlight_old = array_flip($highlight_old);
         $highlight_new = array_flip($highlight_new);
         $renderer->setHighlightOld($highlight_old)->setHighlightNew($highlight_new);
     }
     $renderer->setOriginalOld($this->originalLeft)->setOriginalNew($this->originalRight);
     if ($range_start === null) {
         $range_start = 0;
     }
     if ($range_len === null) {
         $range_len = $rows;
     }
     $range_len = min($range_len, $rows - $range_start);
     list($gaps, $mask, $depths) = $this->calculateGapsMaskAndDepths($mask_force, $feedback_mask, $range_start, $range_len);
     $renderer->setGaps($gaps)->setMask($mask)->setDepths($depths);
     $html = $renderer->renderTextChange($range_start, $range_len, $rows);
     return $renderer->renderChangesetTable($html);
 }
コード例 #10
0
 private function buildContent(PhabricatorPaste $paste)
 {
     $language = $paste->getLanguage();
     $source = $paste->getRawContent();
     if (empty($language)) {
         return PhabricatorSyntaxHighlighter::highlightWithFilename($paste->getTitle(), $source);
     } else {
         return PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source);
     }
 }
コード例 #11
0
 private function buildCorpus($selected, $file_query, $needs_blame, $drequest, $path, $data)
 {
     $image_type = $this->getImageType($path);
     if ($image_type && !$selected) {
         $corpus = phutil_render_tag('img', array('style' => 'padding-bottom: 10px', 'src' => 'data:' . $image_type . ';base64,' . base64_encode($data)));
         return $corpus;
     }
     $document_type = $this->getDocumentType($path);
     if ($document_type && !$selected || !phutil_is_utf8($data)) {
         $data = $file_query->getRawData();
         $document_type_description = $document_type ? $document_type : 'binary';
         $corpus = phutil_render_tag('p', array('style' => 'text-align: center;'), phutil_render_tag('a', array('href' => '?view=raw', 'class' => 'button'), "View {$document_type_description}"));
         return $corpus;
     }
     // TODO: blame of blame.
     switch ($selected) {
         case 'plain':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html($file_query->getRawData()));
             break;
         case 'plainblame':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $rows = array();
             foreach ($text_list as $k => $line) {
                 $rev = $rev_list[$k];
                 if (isset($blame_dict[$rev]['handle'])) {
                     $author = $blame_dict[$rev]['handle']->getName();
                 } else {
                     $author = $blame_dict[$rev]['author'];
                 }
                 $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
             }
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html(implode("\n", $rows)));
             break;
         case 'highlighted':
         case 'blame':
         default:
             require_celerity_resource('syntax-highlighting-css');
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $text_list = implode("\n", $text_list);
             $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $text_list);
             $text_list = explode("\n", $text_list);
             $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected);
             $corpus_table = phutil_render_tag('table', array('class' => "diffusion-source remarkup-code PhabricatorMonospaced"), implode("\n", $rows));
             $corpus = phutil_render_tag('div', array('style' => 'padding: 0pt 2em;'), $corpus_table);
             break;
     }
     return $corpus;
 }
コード例 #12
0
 private function highlightSource($source, $title, $language)
 {
     if (empty($language)) {
         return PhabricatorSyntaxHighlighter::highlightWithFilename($title, $source);
     } else {
         return PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source);
     }
 }
コード例 #13
0
 private function buildCorpus($selected, DiffusionFileContentQuery $file_query, $needs_blame, DiffusionRequest $drequest, $path, $data)
 {
     if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
         $file = $this->loadFileForData($path, $data);
         $file_uri = $file->getBestURI();
         if ($file->isViewableImage()) {
             $this->corpusType = 'image';
             return $this->buildImageCorpus($file_uri);
         } else {
             $this->corpusType = 'binary';
             return $this->buildBinaryCorpus($file_uri, $data);
         }
     }
     switch ($selected) {
         case 'plain':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html($file_query->getRawData()));
             break;
         case 'plainblame':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $rows = array();
             foreach ($text_list as $k => $line) {
                 $rev = $rev_list[$k];
                 if (isset($blame_dict[$rev]['handle'])) {
                     $author = $blame_dict[$rev]['handle']->getName();
                 } else {
                     $author = $blame_dict[$rev]['author'];
                 }
                 $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
             }
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html(implode("\n", $rows)));
             break;
         case 'highlighted':
         case 'blame':
         default:
             require_celerity_resource('syntax-highlighting-css');
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $text_list = implode("\n", $text_list);
             $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $text_list);
             $text_list = explode("\n", $text_list);
             $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected);
             $corpus_table = phutil_render_tag('table', array('class' => "diffusion-source remarkup-code PhabricatorMonospaced"), implode("\n", $rows));
             $corpus = phutil_render_tag('div', array('style' => 'padding: 0 2em;'), $corpus_table);
             break;
     }
     return $corpus;
 }
コード例 #14
0
 private function buildCorpus($selected)
 {
     $needs_blame = $selected == 'blame' || $selected == 'plainblame';
     $file_query = DiffusionFileContentQuery::newFromDiffusionRequest($this->diffusionRequest);
     $file_query->setNeedsBlame($needs_blame);
     $file_query->loadFileContent();
     $drequest = $this->getDiffusionRequest();
     $path = $drequest->getPath();
     $image_type = $this->getImageType($path);
     if ($image_type && !$selected) {
         $data = $file_query->getRawData();
         $corpus = phutil_render_tag('img', array('style' => 'padding-bottom: 10px', 'src' => 'data:' . $image_type . ';base64,' . base64_encode($data)));
         return $corpus;
     }
     // TODO: blame of blame.
     switch ($selected) {
         case 'plain':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html($file_query->getRawData()));
             break;
         case 'plainblame':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $rows = array();
             foreach ($text_list as $k => $line) {
                 $rev = $rev_list[$k];
                 $author = $blame_dict[$rev]['author'];
                 $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
             }
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html(implode("\n", $rows)));
             break;
         case 'highlighted':
         case 'blame':
         default:
             require_celerity_resource('syntax-highlighting-css');
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $text_list = implode("\n", $text_list);
             $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $text_list);
             $text_list = explode("\n", $text_list);
             $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected);
             $corpus_table = phutil_render_tag('table', array('class' => "diffusion-source remarkup-code PhabricatorMonospaced"), implode("\n", $rows));
             $corpus = phutil_render_tag('div', array('style' => 'padding: 0pt 2em;'), $corpus_table);
             break;
     }
     return $corpus;
 }
コード例 #15
0
 private function buildCorpus($selected, DiffusionFileContentQuery $file_query, $needs_blame, DiffusionRequest $drequest, $path, $data)
 {
     if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
         $file = $this->loadFileForData($path, $data);
         $file_uri = $file->getBestURI();
         if ($file->isViewableImage()) {
             $this->corpusType = 'image';
             return $this->buildImageCorpus($file_uri);
         } else {
             $this->corpusType = 'binary';
             return $this->buildBinaryCorpus($file_uri, $data);
         }
     }
     switch ($selected) {
         case 'plain':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html($file_query->getRawData()));
             break;
         case 'plainblame':
             $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $rows = array();
             foreach ($text_list as $k => $line) {
                 $rev = $rev_list[$k];
                 if (isset($blame_dict[$rev]['handle'])) {
                     $author = $blame_dict[$rev]['handle']->getName();
                 } else {
                     $author = $blame_dict[$rev]['author'];
                 }
                 $rows[] = sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
             }
             $corpus = phutil_render_tag('textarea', array('style' => $style), phutil_escape_html(implode("\n", $rows)));
             break;
         case 'highlighted':
         case 'blame':
         default:
             require_celerity_resource('syntax-highlighting-css');
             list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData();
             $text_list = implode("\n", $text_list);
             $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $text_list);
             $text_list = explode("\n", $text_list);
             $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $file_query, $selected);
             $id = celerity_generate_unique_node_id();
             $projects = $drequest->loadArcanistProjects();
             $langs = array();
             foreach ($projects as $project) {
                 $ls = $project->getSymbolIndexLanguages();
                 if (!$ls) {
                     continue;
                 }
                 $dep_projects = $project->getSymbolIndexProjects();
                 $dep_projects[] = $project->getPHID();
                 foreach ($ls as $lang) {
                     if (!isset($langs[$lang])) {
                         $langs[$lang] = array();
                     }
                     $langs[$lang] += $dep_projects + array($project);
                 }
             }
             $lang = last(explode('.', $drequest->getPath()));
             $prefs = $this->getRequest()->getUser()->loadPreferences();
             $pref_symbols = $prefs->getPreference(PhabricatorUserPreferences::PREFERENCE_DIFFUSION_SYMBOLS);
             if (isset($langs[$lang]) && $pref_symbols != 'disabled') {
                 Javelin::initBehavior('repository-crossreference', array('container' => $id, 'lang' => $lang, 'projects' => $langs[$lang]));
             }
             $corpus_table = javelin_render_tag('table', array('class' => "diffusion-source remarkup-code PhabricatorMonospaced", 'sigil' => 'diffusion-source'), implode("\n", $rows));
             $corpus = phutil_render_tag('div', array('style' => 'padding: 0 2em;', 'id' => $id), $corpus_table);
             break;
     }
     return $corpus;
 }
コード例 #16
0
 private function buildCorpus($show_blame, $show_color, DiffusionFileContent $file_content, $needs_blame, DiffusionRequest $drequest, $path, $data)
 {
     if (!$show_color) {
         $style = 'border: none; width: 100%; height: 80em; font-family: monospace';
         if (!$show_blame) {
             $corpus = phutil_tag('textarea', array('style' => $style), $file_content->getCorpus());
         } else {
             $text_list = $file_content->getTextList();
             $rev_list = $file_content->getRevList();
             $blame_dict = $file_content->getBlameDict();
             $rows = array();
             foreach ($text_list as $k => $line) {
                 $rev = $rev_list[$k];
                 $author = $blame_dict[$rev]['author'];
                 $rows[] = sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line);
             }
             $corpus = phutil_tag('textarea', array('style' => $style), implode("\n", $rows));
         }
     } else {
         require_celerity_resource('syntax-highlighting-css');
         $text_list = $file_content->getTextList();
         $rev_list = $file_content->getRevList();
         $blame_dict = $file_content->getBlameDict();
         $text_list = implode("\n", $text_list);
         $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename($path, $text_list);
         $text_list = explode("\n", $text_list);
         $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, $needs_blame, $drequest, $show_blame, $show_color);
         $corpus_table = javelin_tag('table', array('class' => 'diffusion-source remarkup-code PhabricatorMonospaced', 'sigil' => 'phabricator-source'), $rows);
         if ($this->getRequest()->isAjax()) {
             return $corpus_table;
         }
         $id = celerity_generate_unique_node_id();
         $repo = $drequest->getRepository();
         $symbol_repos = nonempty($repo->getSymbolSources(), array());
         $symbol_repos[] = $repo;
         $lang = last(explode('.', $drequest->getPath()));
         $repo_languages = $repo->getSymbolLanguages();
         $repo_languages = nonempty($repo_languages, array());
         $repo_languages = array_fill_keys($repo_languages, true);
         $needs_symbols = true;
         if ($repo_languages && $symbol_repos) {
             $have_symbols = id(new DiffusionSymbolQuery())->existsSymbolsInRepository($repo->getPHID());
             if (!$have_symbols) {
                 $needs_symbols = false;
             }
         }
         if ($needs_symbols && $repo_languages) {
             $needs_symbols = isset($repo_languages[$lang]);
         }
         if ($needs_symbols) {
             Javelin::initBehavior('repository-crossreference', array('container' => $id, 'lang' => $lang, 'repositories' => $symbol_repos));
         }
         $corpus = phutil_tag('div', array('id' => $id), $corpus_table);
         Javelin::initBehavior('load-blame', array('id' => $id));
     }
     $edit = $this->renderEditButton();
     $file = $this->renderFileButton();
     $header = id(new PHUIHeaderView())->setHeader(pht('File Contents'))->addActionLink($edit)->addActionLink($file);
     $corpus = id(new PHUIObjectBoxView())->setHeader($header)->appendChild($corpus);
     return $corpus;
 }
コード例 #17
0
 private function buildSymbolIndexes(PhabricatorRepository $repository, array $visible_changesets)
 {
     assert_instances_of($visible_changesets, 'DifferentialChangeset');
     $engine = PhabricatorSyntaxHighlighter::newEngine();
     $langs = $repository->getSymbolLanguages();
     $langs = nonempty($langs, array());
     $sources = $repository->getSymbolSources();
     $sources = nonempty($sources, array());
     $symbol_indexes = array();
     if ($langs && $sources) {
         $have_symbols = id(new DiffusionSymbolQuery())->existsSymbolsInRepository($repository->getPHID());
         if (!$have_symbols) {
             return $symbol_indexes;
         }
     }
     $repository_phids = array_merge(array($repository->getPHID()), $sources);
     $indexed_langs = array_fill_keys($langs, true);
     foreach ($visible_changesets as $key => $changeset) {
         $lang = $engine->getLanguageFromFilename($changeset->getFilename());
         if (empty($indexed_langs) || isset($indexed_langs[$lang])) {
             $symbol_indexes[$key] = array('lang' => $lang, 'repositories' => $repository_phids);
         }
     }
     return $symbol_indexes;
 }
コード例 #18
0
 public function render($range_start = null, $range_len = null, $mask_force = array())
 {
     // "Top level" renders are initial requests for the whole file, versus
     // requests for a specific range generated by clicking "show more". We
     // generate property changes and "shield" UI elements only for toplevel
     // requests.
     $this->isTopLevel = $range_start === null && $range_len === null;
     $this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
     $encoding = null;
     if ($this->characterEncoding) {
         // We are forcing this changeset to be interpreted with a specific
         // character encoding, so force all the hunks into that encoding and
         // propagate it to the renderer.
         $encoding = $this->characterEncoding;
         foreach ($this->changeset->getHunks() as $hunk) {
             $hunk->forceEncoding($this->characterEncoding);
         }
     } else {
         // We're just using the default, so tell the renderer what that is
         // (by reading the encoding from the first hunk).
         foreach ($this->changeset->getHunks() as $hunk) {
             $encoding = $hunk->getDataEncoding();
             break;
         }
     }
     $this->tryCacheStuff();
     $render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
     $rows = max(count($this->old), count($this->new));
     $renderer = $this->getRenderer()->setChangeset($this->changeset)->setRenderPropertyChangeHeader($render_pch)->setIsTopLevel($this->isTopLevel)->setOldRender($this->oldRender)->setNewRender($this->newRender)->setHunkStartLines($this->hunkStartLines)->setOldChangesetID($this->leftSideChangesetID)->setNewChangesetID($this->rightSideChangesetID)->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)->setCodeCoverage($this->getCoverage())->setRenderingReference($this->getRenderingReference())->setMarkupEngine($this->markupEngine)->setHandles($this->handles)->setOldLines($this->old)->setNewLines($this->new)->setOriginalCharacterEncoding($encoding);
     if ($this->user) {
         $renderer->setUser($this->user);
     }
     $shield = null;
     if ($this->isTopLevel && !$this->comments) {
         if ($this->isGenerated()) {
             $shield = $renderer->renderShield(pht('This file contains generated code, which does not normally ' . 'need to be reviewed.'));
         } else {
             if ($this->isUnchanged()) {
                 $type = 'text';
                 if (!$rows) {
                     // NOTE: Normally, diffs which don't change files do not include
                     // file content (for example, if you "chmod +x" a file and then
                     // run "git show", the file content is not available). Similarly,
                     // if you move a file from A to B without changing it, diffs normally
                     // do not show the file content. In some cases `arc` is able to
                     // synthetically generate content for these diffs, but for raw diffs
                     // we'll never have it so we need to be prepared to not render a link.
                     $type = 'none';
                 }
                 $shield = $renderer->renderShield(pht('The contents of this file were not changed.'), $type);
             } else {
                 if ($this->isWhitespaceOnly()) {
                     $shield = $renderer->renderShield(pht('This file was changed only by adding or removing whitespace.'), 'whitespace');
                 } else {
                     if ($this->isDeleted()) {
                         $shield = $renderer->renderShield(pht('This file was completely deleted.'));
                     } else {
                         if ($this->changeset->getAffectedLineCount() > 2500) {
                             $lines = number_format($this->changeset->getAffectedLineCount());
                             $shield = $renderer->renderShield(pht('This file has a very large number of changes (%s lines).', $lines));
                         }
                     }
                 }
             }
         }
     }
     if ($shield) {
         return $renderer->renderChangesetTable($shield);
     }
     $old_comments = array();
     $new_comments = array();
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
     if ($this->comments) {
         foreach ($this->comments as $comment) {
             $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
             $end = $comment->getLineNumber() + $comment->getLineLength() + self::LINES_CONTEXT;
             $new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
             for ($ii = $start; $ii <= $end; $ii++) {
                 if ($new_side) {
                     $new_mask[$ii] = true;
                 } else {
                     $old_mask[$ii] = true;
                 }
             }
         }
         foreach ($this->old as $ii => $old) {
             if (isset($old['line']) && isset($old_mask[$old['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         foreach ($this->new as $ii => $new) {
             if (isset($new['line']) && isset($new_mask[$new['line']])) {
                 $feedback_mask[$ii] = true;
             }
         }
         $this->comments = msort($this->comments, 'getID');
         foreach ($this->comments as $comment) {
             $final = $comment->getLineNumber() + $comment->getLineLength();
             $final = max(1, $final);
             if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
                 $new_comments[$final][] = $comment;
             } else {
                 $old_comments[$final][] = $comment;
             }
         }
     }
     $renderer->setOldComments($old_comments)->setNewComments($new_comments);
     switch ($this->changeset->getFileType()) {
         case DifferentialChangeType::FILE_IMAGE:
             $old = null;
             $new = null;
             // TODO: Improve the architectural issue as discussed in D955
             // https://secure.phabricator.com/D955
             $reference = $this->getRenderingReference();
             $parts = explode('/', $reference);
             if (count($parts) == 2) {
                 list($id, $vs) = $parts;
             } else {
                 $id = $parts[0];
                 $vs = 0;
             }
             $id = (int) $id;
             $vs = (int) $vs;
             if (!$vs) {
                 $metadata = $this->changeset->getMetadata();
                 $data = idx($metadata, 'attachment-data');
                 $old_phid = idx($metadata, 'old:binary-phid');
                 $new_phid = idx($metadata, 'new:binary-phid');
             } else {
                 $vs_changeset = id(new DifferentialChangeset())->load($vs);
                 $old_phid = null;
                 $new_phid = null;
                 // TODO: This is spooky, see D6851
                 if ($vs_changeset) {
                     $vs_metadata = $vs_changeset->getMetadata();
                     $old_phid = idx($vs_metadata, 'new:binary-phid');
                 }
                 $changeset = id(new DifferentialChangeset())->load($id);
                 if ($changeset) {
                     $metadata = $changeset->getMetadata();
                     $new_phid = idx($metadata, 'new:binary-phid');
                 }
             }
             if ($old_phid || $new_phid) {
                 // grab the files, (micro) optimization for 1 query not 2
                 $file_phids = array();
                 if ($old_phid) {
                     $file_phids[] = $old_phid;
                 }
                 if ($new_phid) {
                     $file_phids[] = $new_phid;
                 }
                 // TODO: (T603) Probably fine to use omnipotent viewer here?
                 $files = id(new PhabricatorFile())->loadAllWhere('phid IN (%Ls)', $file_phids);
                 foreach ($files as $file) {
                     if (empty($file)) {
                         continue;
                     }
                     if ($file->getPHID() == $old_phid) {
                         $old = $file;
                     } else {
                         if ($file->getPHID() == $new_phid) {
                             $new = $file;
                         }
                     }
                 }
             }
             $renderer->attachOldFile($old);
             $renderer->attachNewFile($new);
             return $renderer->renderFileChange($old, $new, $id, $vs);
         case DifferentialChangeType::FILE_DIRECTORY:
         case DifferentialChangeType::FILE_BINARY:
             $output = $renderer->renderChangesetTable(null);
             return $output;
     }
     if ($this->originalLeft && $this->originalRight) {
         list($highlight_old, $highlight_new) = $this->diffOriginals();
         $highlight_old = array_flip($highlight_old);
         $highlight_new = array_flip($highlight_new);
         $renderer->setHighlightOld($highlight_old)->setHighlightNew($highlight_new);
     }
     $renderer->setOriginalOld($this->originalLeft)->setOriginalNew($this->originalRight);
     if ($range_start === null) {
         $range_start = 0;
     }
     if ($range_len === null) {
         $range_len = $rows;
     }
     $range_len = min($range_len, $rows - $range_start);
     list($gaps, $mask, $depths) = $this->calculateGapsMaskAndDepths($mask_force, $feedback_mask, $range_start, $range_len);
     $renderer->setGaps($gaps)->setMask($mask)->setDepths($depths);
     $html = $renderer->renderTextChange($range_start, $range_len, $rows);
     return $renderer->renderChangesetTable($html);
 }