public function render()
 {
     $old = $this->oldText;
     $new = $this->newText;
     // TODO: On mobile, or perhaps by default, we should switch to 1-up once
     // that is built.
     if (strlen($old)) {
         $old = phutil_utf8_hard_wrap($old, 80);
         $old = implode("\n", $old) . "\n";
     }
     if (strlen($new)) {
         $new = phutil_utf8_hard_wrap($new, 80);
         $new = implode("\n", $new) . "\n";
     }
     try {
         $engine = new PhabricatorDifferenceEngine();
         $changeset = $engine->generateChangesetFromFileContent($old, $new);
         $whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
         $markup_engine = new PhabricatorMarkupEngine();
         $markup_engine->setViewer($this->getUser());
         $parser = new DifferentialChangesetParser();
         $parser->setUser($this->getUser());
         $parser->setChangeset($changeset);
         $parser->setMarkupEngine($markup_engine);
         $parser->setWhitespaceMode($whitespace_mode);
         return $parser->render(0, PHP_INT_MAX, array());
     } catch (Exception $ex) {
         return $ex->getMessage();
     }
 }
 public function processRequest()
 {
     $drequest = $this->getDiffusionRequest();
     $request = $this->getRequest();
     $user = $request->getUser();
     if (!$request->isAjax()) {
         // This request came out of the dropdown menu, either "View Standalone"
         // or "View Raw File".
         $view = $request->getStr('view');
         if ($view == 'r') {
             $uri = $drequest->generateURI(array('action' => 'browse', 'params' => array('view' => 'raw')));
         } else {
             $uri = $drequest->generateURI(array('action' => 'change'));
         }
         return id(new AphrontRedirectResponse())->setURI($uri);
     }
     $diff_query = DiffusionDiffQuery::newFromDiffusionRequest($drequest);
     $changeset = $diff_query->loadChangeset();
     if (!$changeset) {
         return new Aphront404Response();
     }
     $parser = new DifferentialChangesetParser();
     $parser->setUser($user);
     $parser->setChangeset($changeset);
     $parser->setRenderingReference($diff_query->getRenderingReference());
     $parser->setMarkupEngine(PhabricatorMarkupEngine::newDiffusionMarkupEngine());
     $pquery = new DiffusionPathIDQuery(array($changeset->getFilename()));
     $ids = $pquery->loadPathIDs();
     $path_id = $ids[$changeset->getFilename()];
     $parser->setLeftSideCommentMapping($path_id, false);
     $parser->setRightSideCommentMapping($path_id, true);
     $parser->setWhitespaceMode(DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
     $inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere('commitPHID = %s AND pathID = %d AND
     (authorPHID = %s OR auditCommentID IS NOT NULL)', $drequest->loadCommit()->getPHID(), $path_id, $user->getPHID());
     if ($inlines) {
         foreach ($inlines as $inline) {
             $parser->parseInlineComment($inline);
         }
         $phids = mpull($inlines, 'getAuthorPHID');
         $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
         $parser->setHandles($handles);
     }
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec);
     $output = $parser->render($range_s, $range_e, $mask);
     return id(new PhabricatorChangesetResponse())->setRenderedChangeset($output);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $author_phid = $request->getUser()->getPHID();
     $rendering_reference = $request->getStr('ref');
     $parts = explode('/', $rendering_reference);
     if (count($parts) == 2) {
         list($id, $vs) = $parts;
     } else {
         $id = $parts[0];
         $vs = 0;
     }
     $id = (int) $id;
     $vs = (int) $vs;
     $changeset = id(new DifferentialChangeset())->load($id);
     if (!$changeset) {
         return new Aphront404Response();
     }
     $view = $request->getStr('view');
     if ($view) {
         $changeset->attachHunks($changeset->loadHunks());
         switch ($view) {
             case 'new':
                 return $this->buildRawFileResponse($changeset->makeNewFile());
             case 'old':
                 return $this->buildRawFileResponse($changeset->makeOldFile());
             default:
                 return new Aphront400Response();
         }
     }
     if ($vs && $vs != -1) {
         $vs_changeset = id(new DifferentialChangeset())->load($vs);
         if (!$vs_changeset) {
             return new Aphront404Response();
         }
     }
     if (!$vs) {
         $right = $changeset;
         $left = null;
         $right_source = $right->getID();
         $right_new = true;
         $left_source = $right->getID();
         $left_new = false;
         $render_cache_key = $right->getID();
     } else {
         if ($vs == -1) {
             $right = null;
             $left = $changeset;
             $right_source = $left->getID();
             $right_new = false;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         } else {
             $right = $changeset;
             $left = $vs_changeset;
             $right_source = $right->getID();
             $right_new = true;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         }
     }
     if ($left) {
         $left->attachHunks($left->loadHunks());
     }
     if ($right) {
         $right->attachHunks($right->loadHunks());
     }
     if ($left) {
         $left_data = $left->makeNewFile();
         if ($right) {
             $right_data = $right->makeNewFile();
         } else {
             $right_data = $left->makeOldFile();
         }
         $engine = new PhabricatorDifferenceEngine();
         $synthetic = $engine->generateChangesetFromFileContent($left_data, $right_data);
         $choice = nonempty($left, $right);
         $choice->attachHunks($synthetic->getHunks());
         $changeset = $choice;
         $changeset->setID(null);
     }
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec);
     $parser = new DifferentialChangesetParser();
     $parser->setChangeset($changeset);
     $parser->setRenderingReference($rendering_reference);
     $parser->setRenderCacheKey($render_cache_key);
     $parser->setRightSideCommentMapping($right_source, $right_new);
     $parser->setLeftSideCommentMapping($left_source, $left_new);
     $parser->setWhitespaceMode($request->getStr('whitespace'));
     // Load both left-side and right-side inline comments.
     $inlines = $this->loadInlineComments(array($left_source, $right_source), $author_phid);
     $phids = array();
     foreach ($inlines as $inline) {
         $parser->parseInlineComment($inline);
         $phids[$inline->getAuthorPHID()] = true;
     }
     $phids = array_keys($phids);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $parser->setHandles($handles);
     $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
     $parser->setMarkupEngine($engine);
     if ($request->isAjax()) {
         // TODO: This is sort of lazy, the effect is just to not render "Edit"
         // links on the "standalone view".
         $parser->setUser($request->getUser());
     }
     $output = $parser->render($range_s, $range_e, $mask);
     if ($request->isAjax()) {
         return id(new AphrontAjaxResponse())->setContent($output);
     }
     Javelin::initBehavior('differential-show-more', array('uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace')));
     Javelin::initBehavior('differential-comment-jump', array());
     $detail = new DifferentialChangesetDetailView();
     $detail->setChangeset($changeset);
     $detail->appendChild($output);
     if (!$vs) {
         $detail->addButton(phutil_render_tag('a', array('href' => $request->getRequestURI()->alter('view', 'old'), 'class' => 'grey button small'), 'View Raw File (Old Version)'));
         $detail->addButton(phutil_render_tag('a', array('href' => $request->getRequestURI()->alter('view', 'new'), 'class' => 'grey button small'), 'View Raw File (New Version)'));
     }
     $detail->setRevisionID($request->getInt('revision_id'));
     $output = id(new DifferentialPrimaryPaneView())->setLineWidthFromChangesets(array($changeset))->appendChild('<div class="differential-review-stage" ' . 'id="differential-review-stage">' . $detail->render() . '</div>');
     return $this->buildStandardPageResponse(array($output), array('title' => 'Changeset View'));
 }
 private function buildChangesetParsers($type, $data, $file)
 {
     $parser = new ArcanistDiffParser();
     $changes = $parser->parseDiff($data);
     $diff = DifferentialDiff::newFromRawChanges(PhabricatorUser::getOmnipotentUser(), $changes);
     $changesets = $diff->getChangesets();
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer(new PhabricatorUser());
     $parsers = array();
     foreach ($changesets as $changeset) {
         $cparser = new DifferentialChangesetParser();
         $cparser->setUser(new PhabricatorUser());
         $cparser->setDisableCache(true);
         $cparser->setChangeset($changeset);
         $cparser->setMarkupEngine($engine);
         if ($type == 'one') {
             $cparser->setRenderer(new DifferentialChangesetOneUpTestRenderer());
         } else {
             if ($type == 'two') {
                 $cparser->setRenderer(new DifferentialChangesetTwoUpTestRenderer());
             } else {
                 throw new Exception(pht('Unknown renderer type "%s"!', $type));
             }
         }
         $parsers[] = $cparser;
     }
     return $parsers;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $author_phid = $request->getUser()->getPHID();
     $rendering_reference = $request->getStr('ref');
     $parts = explode('/', $rendering_reference);
     if (count($parts) == 2) {
         list($id, $vs) = $parts;
     } else {
         $id = $parts[0];
         $vs = 0;
     }
     $id = (int) $id;
     $vs = (int) $vs;
     $changeset = id(new DifferentialChangeset())->load($id);
     if (!$changeset) {
         return new Aphront404Response();
     }
     $view = $request->getStr('view');
     if ($view) {
         $changeset->attachHunks($changeset->loadHunks());
         $phid = idx($changeset->getMetadata(), "{$view}:binary-phid");
         if ($phid) {
             return id(new AphrontRedirectResponse())->setURI("/file/info/{$phid}/");
         }
         switch ($view) {
             case 'new':
                 return $this->buildRawFileResponse($changeset, $is_new = true);
             case 'old':
                 if ($vs && $vs != -1) {
                     $vs_changeset = id(new DifferentialChangeset())->load($vs);
                     if ($vs_changeset) {
                         $vs_changeset->attachHunks($vs_changeset->loadHunks());
                         return $this->buildRawFileResponse($vs_changeset, $is_new = true);
                     }
                 }
                 return $this->buildRawFileResponse($changeset, $is_new = false);
             default:
                 return new Aphront400Response();
         }
     }
     if ($vs && $vs != -1) {
         $vs_changeset = id(new DifferentialChangeset())->load($vs);
         if (!$vs_changeset) {
             return new Aphront404Response();
         }
     }
     if (!$vs) {
         $right = $changeset;
         $left = null;
         $right_source = $right->getID();
         $right_new = true;
         $left_source = $right->getID();
         $left_new = false;
         $render_cache_key = $right->getID();
     } else {
         if ($vs == -1) {
             $right = null;
             $left = $changeset;
             $right_source = $left->getID();
             $right_new = false;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         } else {
             $right = $changeset;
             $left = $vs_changeset;
             $right_source = $right->getID();
             $right_new = true;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         }
     }
     if ($left) {
         $left->attachHunks($left->loadHunks());
     }
     if ($right) {
         $right->attachHunks($right->loadHunks());
     }
     if ($left) {
         $left_data = $left->makeNewFile();
         if ($right) {
             $right_data = $right->makeNewFile();
         } else {
             $right_data = $left->makeOldFile();
         }
         $engine = new PhabricatorDifferenceEngine();
         $synthetic = $engine->generateChangesetFromFileContent($left_data, $right_data);
         $choice = clone nonempty($left, $right);
         $choice->attachHunks($synthetic->getHunks());
         $changeset = $choice;
     }
     $coverage = null;
     if ($right && $right->getDiffID()) {
         $unit = id(new DifferentialDiffProperty())->loadOneWhere('diffID = %d AND name = %s', $right->getDiffID(), 'arc:unit');
         if ($unit) {
             $coverage = array();
             foreach ($unit->getData() as $result) {
                 $result_coverage = idx($result, 'coverage');
                 if (!$result_coverage) {
                     continue;
                 }
                 $file_coverage = idx($result_coverage, $right->getFileName());
                 if (!$file_coverage) {
                     continue;
                 }
                 $coverage[] = $file_coverage;
             }
             $coverage = ArcanistUnitTestResult::mergeCoverage($coverage);
         }
     }
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec);
     $parser = new DifferentialChangesetParser();
     $parser->setCoverage($coverage);
     $parser->setChangeset($changeset);
     $parser->setRenderingReference($rendering_reference);
     $parser->setRenderCacheKey($render_cache_key);
     $parser->setRightSideCommentMapping($right_source, $right_new);
     $parser->setLeftSideCommentMapping($left_source, $left_new);
     $parser->setWhitespaceMode($request->getStr('whitespace'));
     if ($left && $right) {
         $parser->setOriginals($left, $right);
     }
     // Load both left-side and right-side inline comments.
     $inlines = $this->loadInlineComments(array($left_source, $right_source), $author_phid);
     if ($left_new) {
         $inlines = array_merge($inlines, $this->buildLintInlineComments($left));
     }
     if ($right_new) {
         $inlines = array_merge($inlines, $this->buildLintInlineComments($right));
     }
     $phids = array();
     foreach ($inlines as $inline) {
         $parser->parseInlineComment($inline);
         if ($inline->getAuthorPHID()) {
             $phids[$inline->getAuthorPHID()] = true;
         }
     }
     $phids = array_keys($phids);
     $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
     $parser->setHandles($handles);
     $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
     $parser->setMarkupEngine($engine);
     if ($request->isAjax()) {
         // TODO: This is sort of lazy, the effect is just to not render "Edit"
         // and "Reply" links on the "standalone view".
         $parser->setUser($request->getUser());
     }
     $output = $parser->render($range_s, $range_e, $mask);
     $mcov = $parser->renderModifiedCoverage();
     if ($request->isAjax()) {
         $coverage = array('differential-mcoverage-' . md5($changeset->getFilename()) => $mcov);
         return id(new PhabricatorChangesetResponse())->setRenderedChangeset($output)->setCoverage($coverage);
     }
     Javelin::initBehavior('differential-show-more', array('uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace')));
     Javelin::initBehavior('differential-comment-jump', array());
     $detail = new DifferentialChangesetDetailView();
     $detail->setChangeset($changeset);
     $detail->appendChild($output);
     $detail->setVsChangesetID($left_source);
     $output = id(new DifferentialPrimaryPaneView())->setLineWidthFromChangesets(array($changeset))->appendChild('<div class="differential-review-stage" ' . 'id="differential-review-stage">' . $detail->render() . '</div>');
     return $this->buildStandardPageResponse(array($output), array('title' => 'Changeset View'));
 }
 public function handleRequest(AphrontRequest $request)
 {
     $response = $this->loadDiffusionContext();
     if ($response) {
         return $response;
     }
     $viewer = $this->getViewer();
     $drequest = $this->getDiffusionRequest();
     if (!$request->isAjax()) {
         // This request came out of the dropdown menu, either "View Standalone"
         // or "View Raw File".
         $view = $request->getStr('view');
         if ($view == 'r') {
             $uri = $drequest->generateURI(array('action' => 'browse', 'params' => array('view' => 'raw')));
         } else {
             $uri = $drequest->generateURI(array('action' => 'change'));
         }
         return id(new AphrontRedirectResponse())->setURI($uri);
     }
     $data = $this->callConduitWithDiffusionRequest('diffusion.diffquery', array('commit' => $drequest->getCommit(), 'path' => $drequest->getPath()));
     $drequest->updateSymbolicCommit($data['effectiveCommit']);
     $raw_changes = ArcanistDiffChange::newFromConduit($data['changes']);
     $diff = DifferentialDiff::newEphemeralFromRawChanges($raw_changes);
     $changesets = $diff->getChangesets();
     $changeset = reset($changesets);
     if (!$changeset) {
         return new Aphront404Response();
     }
     $parser = new DifferentialChangesetParser();
     $parser->setUser($viewer);
     $parser->setChangeset($changeset);
     $parser->setRenderingReference($drequest->generateURI(array('action' => 'rendering-ref')));
     $parser->readParametersFromRequest($request);
     $coverage = $drequest->loadCoverage();
     if ($coverage) {
         $parser->setCoverage($coverage);
     }
     $commit = $drequest->loadCommit();
     $pquery = new DiffusionPathIDQuery(array($changeset->getFilename()));
     $ids = $pquery->loadPathIDs();
     $path_id = $ids[$changeset->getFilename()];
     $parser->setLeftSideCommentMapping($path_id, false);
     $parser->setRightSideCommentMapping($path_id, true);
     $parser->setCanMarkDone($commit->getAuthorPHID() && $viewer->getPHID() == $commit->getAuthorPHID());
     $parser->setObjectOwnerPHID($commit->getAuthorPHID());
     $parser->setWhitespaceMode(DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
     $inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments($viewer, $commit->getPHID(), $path_id);
     if ($inlines) {
         foreach ($inlines as $inline) {
             $parser->parseInlineComment($inline);
         }
         $phids = mpull($inlines, 'getAuthorPHID');
         $handles = $this->loadViewerHandles($phids);
         $parser->setHandles($handles);
     }
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($viewer);
     foreach ($inlines as $inline) {
         $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
     }
     $engine->process();
     $parser->setMarkupEngine($engine);
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec);
     $parser->setRange($range_s, $range_e);
     $parser->setMask($mask);
     return id(new PhabricatorChangesetResponse())->setRenderedChangeset($parser->renderChangeset())->setUndoTemplates($parser->getRenderer()->renderUndoTemplates());
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $author_phid = $request->getUser()->getPHID();
     $rendering_reference = $request->getStr('ref');
     $parts = explode('/', $rendering_reference);
     if (count($parts) == 2) {
         list($id, $vs) = $parts;
     } else {
         $id = $parts[0];
         $vs = 0;
     }
     $id = (int) $id;
     $vs = (int) $vs;
     $load_ids = array($id);
     if ($vs && $vs != -1) {
         $load_ids[] = $vs;
     }
     $changesets = id(new DifferentialChangesetQuery())->setViewer($request->getUser())->withIDs($load_ids)->needHunks(true)->execute();
     $changesets = mpull($changesets, null, 'getID');
     $changeset = idx($changesets, $id);
     if (!$changeset) {
         return new Aphront404Response();
     }
     $vs_changeset = null;
     if ($vs && $vs != -1) {
         $vs_changeset = idx($changesets, $vs);
         if (!$vs_changeset) {
             return new Aphront404Response();
         }
     }
     $view = $request->getStr('view');
     if ($view) {
         $phid = idx($changeset->getMetadata(), "{$view}:binary-phid");
         if ($phid) {
             return id(new AphrontRedirectResponse())->setURI("/file/info/{$phid}/");
         }
         switch ($view) {
             case 'new':
                 return $this->buildRawFileResponse($changeset, $is_new = true);
             case 'old':
                 if ($vs_changeset) {
                     return $this->buildRawFileResponse($vs_changeset, $is_new = true);
                 }
                 return $this->buildRawFileResponse($changeset, $is_new = false);
             default:
                 return new Aphront400Response();
         }
     }
     if (!$vs) {
         $right = $changeset;
         $left = null;
         $right_source = $right->getID();
         $right_new = true;
         $left_source = $right->getID();
         $left_new = false;
         $render_cache_key = $right->getID();
     } else {
         if ($vs == -1) {
             $right = null;
             $left = $changeset;
             $right_source = $left->getID();
             $right_new = false;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         } else {
             $right = $changeset;
             $left = $vs_changeset;
             $right_source = $right->getID();
             $right_new = true;
             $left_source = $left->getID();
             $left_new = true;
             $render_cache_key = null;
         }
     }
     if ($left) {
         $left_data = $left->makeNewFile();
         if ($right) {
             $right_data = $right->makeNewFile();
         } else {
             $right_data = $left->makeOldFile();
         }
         $engine = new PhabricatorDifferenceEngine();
         $synthetic = $engine->generateChangesetFromFileContent($left_data, $right_data);
         $choice = clone nonempty($left, $right);
         $choice->attachHunks($synthetic->getHunks());
         $changeset = $choice;
     }
     $coverage = null;
     if ($right && $right->getDiffID()) {
         $unit = id(new DifferentialDiffProperty())->loadOneWhere('diffID = %d AND name = %s', $right->getDiffID(), 'arc:unit');
         if ($unit) {
             $coverage = array();
             foreach ($unit->getData() as $result) {
                 $result_coverage = idx($result, 'coverage');
                 if (!$result_coverage) {
                     continue;
                 }
                 $file_coverage = idx($result_coverage, $right->getFileName());
                 if (!$file_coverage) {
                     continue;
                 }
                 $coverage[] = $file_coverage;
             }
             $coverage = ArcanistUnitTestResult::mergeCoverage($coverage);
         }
     }
     $spec = $request->getStr('range');
     list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec);
     $parser = new DifferentialChangesetParser();
     $parser->setCoverage($coverage);
     $parser->setChangeset($changeset);
     $parser->setRenderingReference($rendering_reference);
     $parser->setRenderCacheKey($render_cache_key);
     $parser->setRightSideCommentMapping($right_source, $right_new);
     $parser->setLeftSideCommentMapping($left_source, $left_new);
     $parser->setWhitespaceMode($request->getStr('whitespace'));
     $parser->setCharacterEncoding($request->getStr('encoding'));
     $parser->setHighlightAs($request->getStr('highlight'));
     if ($request->getStr('renderer') == '1up') {
         $parser->setRenderer(new DifferentialChangesetOneUpRenderer());
     }
     if ($left && $right) {
         $parser->setOriginals($left, $right);
     }
     // Load both left-side and right-side inline comments.
     $inlines = $this->loadInlineComments(array($left_source, $right_source), $author_phid);
     if ($left_new) {
         $inlines = array_merge($inlines, $this->buildLintInlineComments($left));
     }
     if ($right_new) {
         $inlines = array_merge($inlines, $this->buildLintInlineComments($right));
     }
     $phids = array();
     foreach ($inlines as $inline) {
         $parser->parseInlineComment($inline);
         if ($inline->getAuthorPHID()) {
             $phids[$inline->getAuthorPHID()] = true;
         }
     }
     $phids = array_keys($phids);
     $handles = $this->loadViewerHandles($phids);
     $parser->setHandles($handles);
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($request->getUser());
     foreach ($inlines as $inline) {
         $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
     }
     $engine->process();
     $parser->setMarkupEngine($engine);
     if ($request->isAjax()) {
         // TODO: This is sort of lazy, the effect is just to not render "Edit"
         // and "Reply" links on the "standalone view".
         $parser->setUser($request->getUser());
     }
     $output = $parser->render($range_s, $range_e, $mask);
     $mcov = $parser->renderModifiedCoverage();
     if ($request->isAjax()) {
         $coverage = array('differential-mcoverage-' . md5($changeset->getFilename()) => $mcov);
         return id(new PhabricatorChangesetResponse())->setRenderedChangeset($output)->setCoverage($coverage);
     }
     Javelin::initBehavior('differential-show-more', array('uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace')));
     Javelin::initBehavior('differential-comment-jump', array());
     // TODO: [HTML] Clean up DifferentialChangesetParser output, but it's
     // undergoing like six kinds of refactoring anyway.
     $output = phutil_safe_html($output);
     $detail = new DifferentialChangesetDetailView();
     $detail->setChangeset($changeset);
     $detail->appendChild($output);
     $detail->setVsChangesetID($left_source);
     $panel = new DifferentialPrimaryPaneView();
     $panel->appendChild(phutil_tag('div', array('class' => 'differential-review-stage', 'id' => 'differential-review-stage'), $detail->render()));
     $crumbs = $this->buildApplicationCrumbs();
     $revision_id = $changeset->getDiff()->getRevisionID();
     if ($revision_id) {
         $crumbs->addTextCrumb('D' . $revision_id, '/D' . $revision_id);
     }
     $diff_id = $changeset->getDiff()->getID();
     if ($diff_id) {
         $crumbs->addTextCrumb(pht('Diff %d', $diff_id), $this->getApplicationURI('diff/' . $diff_id));
     }
     $crumbs->addTextCrumb($changeset->getDisplayFilename());
     $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Standalone View'))->appendChild($panel);
     return $this->buildApplicationPage(array($crumbs, $box), array('title' => pht('Changeset View'), 'device' => false));
 }