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; }
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); }
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; }
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; }
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); }
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); }
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; }
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); }
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); }
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); } }
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; }
private function highlightSource($source, $title, $language) { if (empty($language)) { return PhabricatorSyntaxHighlighter::highlightWithFilename($title, $source); } else { return PhabricatorSyntaxHighlighter::highlightWithLanguage($language, $source); } }
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; }
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; }
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; }
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; }
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; }
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); }