diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
index 43a8135354..07b12b7172 100644
--- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
+++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
@@ -1,640 +1,640 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 /**
  * @group aphront
  */
 class AphrontDefaultApplicationConfiguration
   extends AphrontApplicationConfiguration {
 
   public function __construct() {
 
   }
 
   public function getApplicationName() {
     return 'aphront-default';
   }
 
   public function getURIMap() {
     return $this->getResourceURIMapRules() + array(
       '/(?:(?P<filter>jump)/)?$' =>
         'PhabricatorDirectoryMainController',
       '/(?:(?P<filter>feed)/)' => array(
         'public/$' => 'PhabricatorFeedPublicStreamController',
         '(?:(?P<subfilter>[^/]+)/)?$' =>
           'PhabricatorDirectoryMainController',
       ),
       '/directory/' => array(
         '(?P<id>\d+)/$'
           => 'PhabricatorDirectoryCategoryViewController',
         'edit/$'
           => 'PhabricatorDirectoryEditController',
         'item/edit/(?:(?P<id>\d+)/)?$'
           => 'PhabricatorDirectoryItemEditController',
         'item/delete/(?P<id>\d+)/'
           => 'PhabricatorDirectoryItemDeleteController',
         'category/edit/(?:(?P<id>\d+)/)?$'
           => 'PhabricatorDirectoryCategoryEditController',
         'category/delete/(?P<id>\d+)/'
           => 'PhabricatorDirectoryCategoryDeleteController',
       ),
       '/file/' => array(
         '$' => 'PhabricatorFileListController',
         'filter/(?P<filter>\w+)/$' => 'PhabricatorFileListController',
         'upload/$' => 'PhabricatorFileUploadController',
         'dropupload/$' => 'PhabricatorFileDropUploadController',
         'delete/(?P<id>\d+)/$' => 'PhabricatorFileDeleteController',
         'info/(?P<phid>[^/]+)/' => 'PhabricatorFileInfoController',
 
         'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/'
           => 'PhabricatorFileDataController',
         // TODO: This is a deprecated version of /data/. Remove it after
         // old links have had a chance to rot.
         'alt/(?P<key>[^/]+)/(?P<phid>[^/]+)/'
           => 'PhabricatorFileDataController',
 
         'macro/' => array(
           '$' => 'PhabricatorFileMacroListController',
           'edit/(?:(?P<id>\d+)/)?$' => 'PhabricatorFileMacroEditController',
           'delete/(?P<id>\d+)/$' => 'PhabricatorFileMacroDeleteController',
         ),
         'proxy/$' => 'PhabricatorFileProxyController',
         'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/'
           => 'PhabricatorFileTransformController',
       ),
       '/phid/' => array(
         '$' => 'PhabricatorPHIDLookupController',
       ),
       '/people/' => array(
         '$' => 'PhabricatorPeopleListController',
         'logs/$' => 'PhabricatorPeopleLogsController',
         'edit/(?:(?P<id>\d+)/(?:(?P<view>\w+)/)?)?$'
           => 'PhabricatorPeopleEditController',
       ),
       '/p/(?P<username>\w+)/(?:(?P<page>\w+)/)?$'
         => 'PhabricatorPeopleProfileController',
       '/conduit/' => array(
         '$' => 'PhabricatorConduitConsoleController',
         'method/(?P<method>[^/]+)/$' => 'PhabricatorConduitConsoleController',
         'log/$' => 'PhabricatorConduitLogController',
         'log/view/(?P<view>[^/]+)/$' => 'PhabricatorConduitLogController',
         'token/$' => 'PhabricatorConduitTokenController',
       ),
       '/api/(?P<method>[^/]+)$' => 'PhabricatorConduitAPIController',
 
       '/D(?P<id>\d+)' => 'DifferentialRevisionViewController',
       '/differential/' => array(
         '$' => 'DifferentialRevisionListController',
         'filter/(?P<filter>\w+)/$' => 'DifferentialRevisionListController',
         'stats/(?P<filter>\w+)/$' => 'DifferentialRevisionStatsController',
         'diff/' => array(
           '(?P<id>\d+)/$' => 'DifferentialDiffViewController',
           'create/$' => 'DifferentialDiffCreateController',
         ),
         'changeset/$'  => 'DifferentialChangesetViewController',
         'revision/edit/(?:(?P<id>\d+)/)?$'
           => 'DifferentialRevisionEditController',
         'comment/' => array(
           'preview/(?P<id>\d+)/$' => 'DifferentialCommentPreviewController',
           'save/$' => 'DifferentialCommentSaveController',
           'inline/' => array(
             'preview/(?P<id>\d+)/$' =>
               'DifferentialInlineCommentPreviewController',
             'edit/(?P<id>\d+)/$' => 'DifferentialInlineCommentEditController',
           ),
         ),
         'subscribe/(?P<action>add|rem)/(?P<id>\d+)/$'
           => 'DifferentialSubscribeController',
       ),
 
       '/typeahead/' => array(
         'common/(?P<type>\w+)/$'
           => 'PhabricatorTypeaheadCommonDatasourceController',
       ),
 
       '/mail/' => array(
         '$' => 'PhabricatorMetaMTAListController',
         'send/$' => 'PhabricatorMetaMTASendController',
         'view/(?P<id>\d+)/$' => 'PhabricatorMetaMTAViewController',
         'lists/$' => 'PhabricatorMetaMTAMailingListsController',
         'lists/edit/(?:(?P<id>\d+)/)?$'
           => 'PhabricatorMetaMTAMailingListEditController',
         'receive/$' => 'PhabricatorMetaMTAReceiveController',
         'received/$' => 'PhabricatorMetaMTAReceivedListController',
         'sendgrid/$' => 'PhabricatorMetaMTASendGridReceiveController',
       ),
 
       '/login/' => array(
         '$' => 'PhabricatorLoginController',
         'email/$' => 'PhabricatorEmailLoginController',
         'etoken/(?P<token>\w+)/$' => 'PhabricatorEmailTokenController',
         'refresh/$' => 'PhabricatorRefreshCSRFController',
         'validate/$' => 'PhabricatorLoginValidateController',
       ),
 
       '/logout/$' => 'PhabricatorLogoutController',
 
       '/oauth/' => array(
         '(?P<provider>\w+)/' => array(
           'login/$'     => 'PhabricatorOAuthLoginController',
           'diagnose/$'  => 'PhabricatorOAuthDiagnosticsController',
           'unlink/$'    => 'PhabricatorOAuthUnlinkController',
         ),
       ),
 
       '/oauthserver/' => array(
         'auth/'          => 'PhabricatorOAuthServerAuthController',
         'test/'          => 'PhabricatorOAuthServerTestController',
         'token/'         => 'PhabricatorOAuthServerTokenController',
         'clientauthorization/' => array(
           '$' => 'PhabricatorOAuthClientAuthorizationListController',
           'delete/(?P<phid>[^/]+)/' =>
             'PhabricatorOAuthClientAuthorizationDeleteController',
           'edit/(?P<phid>[^/]+)/' =>
             'PhabricatorOAuthClientAuthorizationEditController',
         ),
         'client/' => array(
           '$'                        => 'PhabricatorOAuthClientListController',
           'create/$'                 => 'PhabricatorOAuthClientEditController',
           'delete/(?P<phid>[^/]+)/$' =>
             'PhabricatorOAuthClientDeleteController',
           'edit/(?P<phid>[^/]+)/$'   => 'PhabricatorOAuthClientEditController',
           'view/(?P<phid>[^/]+)/$'   => 'PhabricatorOAuthClientViewController',
         ),
       ),
 
       '/xhprof/' => array(
         'profile/(?P<phid>[^/]+)/$' => 'PhabricatorXHProfProfileController',
       ),
 
       '/~/' => 'DarkConsoleController',
 
       '/settings/' => array(
         '(?:page/(?P<page>[^/]+)/)?$' => 'PhabricatorUserSettingsController',
       ),
 
       '/maniphest/' => array(
         '$' => 'ManiphestTaskListController',
         'view/(?P<view>\w+)/$' => 'ManiphestTaskListController',
         'report/(?:(?P<view>\w+)/)?$' => 'ManiphestReportController',
         'batch/$' => 'ManiphestBatchEditController',
         'task/' => array(
           'create/$' => 'ManiphestTaskEditController',
           'edit/(?P<id>\d+)/$' => 'ManiphestTaskEditController',
           'descriptionchange/(?P<id>\d+)/$' =>
             'ManiphestTaskDescriptionChangeController',
           'descriptiondiff/$' =>
             'ManiphestTaskDescriptionDiffController',
           'descriptionpreview/$' =>
             'ManiphestTaskDescriptionPreviewController',
         ),
         'transaction/' => array(
           'save/' => 'ManiphestTransactionSaveController',
           'preview/(?P<id>\d+)/$' => 'ManiphestTransactionPreviewController',
         ),
       ),
 
       '/T(?P<id>\d+)$' => 'ManiphestTaskDetailController',
 
       '/repository/' => array(
         '$'                     => 'PhabricatorRepositoryListController',
         'create/$'              => 'PhabricatorRepositoryCreateController',
         'edit/(?P<id>\d+)/(?:(?P<view>\w+)?/)?$' =>
           'PhabricatorRepositoryEditController',
         'delete/(?P<id>\d+)/$'  => 'PhabricatorRepositoryDeleteController',
         'project/(?P<id>\d+)/' =>
           'PhabricatorRepositoryArcanistProjectEditController',
       ),
 
       '/search/' => array(
         '$' => 'PhabricatorSearchController',
         '(?P<key>[^/]+)/$' => 'PhabricatorSearchController',
         'attach/(?P<phid>[^/]+)/(?P<type>\w+)/(?:(?P<action>\w+)/)?$'
           => 'PhabricatorSearchAttachController',
         'select/(?P<type>\w+)/$'
           => 'PhabricatorSearchSelectController',
         'index/(?P<phid>[^/]+)/$' => 'PhabricatorSearchIndexController',
       ),
 
       '/project/' => array(
         '$' => 'PhabricatorProjectListController',
         'filter/(?P<filter>[^/]+)/$' => 'PhabricatorProjectListController',
         'edit/(?P<id>\d+)/$' => 'PhabricatorProjectProfileEditController',
         'view/(?P<id>\d+)/(?:(?P<page>\w+)/)?$'
           => 'PhabricatorProjectProfileController',
         'create/$' => 'PhabricatorProjectCreateController',
         'update/(?P<id>\d+)/(?P<action>[^/]+)/$'
           => 'PhabricatorProjectUpdateController',
       ),
 
       '/r(?P<callsign>[A-Z]+)(?P<commit>[a-z0-9]+)$'
         => 'DiffusionCommitController',
       '/diffusion/' => array(
         '$' => 'DiffusionHomeController',
         '(?P<callsign>[A-Z]+)/' => array(
           '$' => 'DiffusionRepositoryController',
           'repository/'.
             '(?P<path>[^/]+)/'.
             '$'
               => 'DiffusionRepositoryController',
           'change/'.
             '(?P<path>.*?)'.
             '(?:[;](?P<commit>[a-z0-9]+))?'.
             '$'
               => 'DiffusionChangeController',
           'history/'.
             '(?P<path>.*?)'.
             '(?:[;](?P<commit>[a-z0-9]+))?'.
             '$'
               => 'DiffusionHistoryController',
           'browse/'.
             '(?P<path>.*?)'.
             '(?:[;](?P<commit>[a-z0-9]+))?'.
-            '(?:[$](?P<line>\d+))?'.
+            '(?:[$](?P<line>\d+(?:-\d+)?))?'.
             '$'
               => 'DiffusionBrowseController',
           'diff/'.
             '(?P<path>.*?)'.
             '(?:[;](?P<commit>[a-z0-9]+))?'.
             '$'
               => 'DiffusionDiffController',
           'lastmodified/'.
             '(?P<path>.*?)'.
             '(?:[;](?P<commit>[a-z0-9]+))?'.
             '$'
               => 'DiffusionLastModifiedController',
         ),
         'services/' => array(
           'path/' => array(
             'complete/$' => 'DiffusionPathCompleteController',
             'validate/$' => 'DiffusionPathValidateController',
           ),
         ),
         'author/' => array(
           '$' => 'DiffusionCommitListController',
           '(?P<username>\w+)/$' => 'DiffusionCommitListController',
         ),
         'symbol/(?P<name>[^/]+)/$' => 'DiffusionSymbolController',
       ),
 
       '/daemon/' => array(
         'task/(?P<id>\d+)/$' => 'PhabricatorWorkerTaskDetailController',
         'task/(?P<id>\d+)/(?P<action>[^/]+)/$'
           => 'PhabricatorWorkerTaskUpdateController',
         'log/' => array(
           '$' => 'PhabricatorDaemonLogListController',
           'combined/$' => 'PhabricatorDaemonCombinedLogController',
           '(?P<id>\d+)/$' => 'PhabricatorDaemonLogViewController',
         ),
         'timeline/$' => 'PhabricatorDaemonTimelineConsoleController',
         'timeline/(?P<id>\d+)/$' => 'PhabricatorDaemonTimelineEventController',
         '$' => 'PhabricatorDaemonConsoleController',
       ),
 
       '/herald/' => array(
         '$' => 'HeraldHomeController',
         'view/(?P<view>[^/]+)/' => array(
           '$' => 'HeraldHomeController',
           '(?P<global>global)/$' => 'HeraldHomeController'
         ),
         'new/(?:(?P<type>[^/]+)/)?$' => 'HeraldNewController',
         'rule/(?:(?P<id>\d+)/)?$' => 'HeraldRuleController',
         'history/(?P<id>\d+)/$' => 'HeraldRuleEditHistoryController',
         'delete/(?P<id>\d+)/$' => 'HeraldDeleteController',
         'test/$' => 'HeraldTestConsoleController',
         'all/' => array(
           '$' => 'HeraldAllRulesController',
           'view/(?P<view>[^/]+)/$' => 'HeraldAllRulesController',
         ),
         'transcript/$' => 'HeraldTranscriptListController',
         'transcript/(?P<id>\d+)/(?:(?P<filter>\w+)/)?$'
           => 'HeraldTranscriptController',
       ),
 
       '/uiexample/' => array(
         '$' => 'PhabricatorUIExampleRenderController',
         'view/(?P<class>[^/]+)/$' => 'PhabricatorUIExampleRenderController',
       ),
 
       '/owners/' => array(
         '$' => 'PhabricatorOwnersListController',
         'view/(?P<view>[^/]+)/$' => 'PhabricatorOwnersListController',
         'edit/(?P<id>\d+)/$' => 'PhabricatorOwnersEditController',
         'new/$' => 'PhabricatorOwnersEditController',
         'package/(?P<id>\d+)/$' => 'PhabricatorOwnersDetailController',
         'delete/(?P<id>\d+)/$' => 'PhabricatorOwnersDeleteController',
         '(?P<scope>related|attention)/' => array(
           '$' => 'PhabricatorOwnerRelatedListController',
           '(?P<view>package|owner)/$'
             => 'PhabricatorOwnerRelatedListController',
         ),
       ),
 
       '/audit/' => array(
         '$' => 'PhabricatorAuditListController',
         'view/(?P<filter>[^/]+)/$' => 'PhabricatorAuditListController',
 
         'edit/$' => 'PhabricatorAuditEditController',
         'addcomment/$' => 'PhabricatorAuditAddCommentController',
       ),
 
       '/xhpast/' => array(
         '$' => 'PhabricatorXHPASTViewRunController',
         'view/(?P<id>\d+)/$'
           => 'PhabricatorXHPASTViewFrameController',
         'frameset/(?P<id>\d+)/$'
           => 'PhabricatorXHPASTViewFramesetController',
         'input/(?P<id>\d+)/$'
           => 'PhabricatorXHPASTViewInputController',
         'tree/(?P<id>\d+)/$'
           => 'PhabricatorXHPASTViewTreeController',
         'stream/(?P<id>\d+)/$'
           => 'PhabricatorXHPASTViewStreamController',
       ),
 
       '/status/$' => 'PhabricatorStatusController',
 
       '/paste/' => array(
         '$' => 'PhabricatorPasteListController',
         'filter/(?P<filter>\w+)/$' => 'PhabricatorPasteListController',
       ),
       '/P(?P<id>\d+)$' => 'PhabricatorPasteViewController',
 
       '/help/' => array(
         'keyboardshortcut/$' => 'PhabricatorHelpKeyboardShortcutController',
       ),
 
       '/countdown/' => array(
         '$'
           => 'PhabricatorCountdownListController',
         '(?P<id>\d+)/$'
           => 'PhabricatorCountdownViewController',
         'edit/(?:(?P<id>\d+)/)?$'
           => 'PhabricatorCountdownEditController',
         'delete/(?P<id>\d+)/$'
           => 'PhabricatorCountdownDeleteController'
       ),
 
       '/V(?P<id>\d+)$'  => 'PhabricatorSlowvotePollController',
       '/vote/' => array(
         '(?:view/(?P<view>\w+)/)?$' => 'PhabricatorSlowvoteListController',
         'create/'   => 'PhabricatorSlowvoteCreateController',
       ),
 
       // Match "/w/" with slug "/".
       '/w(?P<slug>/)$'    => 'PhrictionDocumentController',
       // Match "/w/x/y/z/" with slug "x/y/z/".
       '/w/(?P<slug>.+/)$' => 'PhrictionDocumentController',
 
       '/phriction/' => array(
         '$'                       => 'PhrictionListController',
         'list/(?P<view>[^/]+)/$'  => 'PhrictionListController',
 
         'history(?P<slug>/)$'     => 'PhrictionHistoryController',
         'history/(?P<slug>.+/)$'  => 'PhrictionHistoryController',
 
         'edit/(?:(?P<id>\d+)/)?$' => 'PhrictionEditController',
         'delete/(?P<id>\d+)/$'    => 'PhrictionDeleteController',
 
         'preview/$' => 'PhrictionDocumentPreviewController',
         'diff/(?P<id>\d+)/$' => 'PhrictionDiffController',
       ),
 
       '/calendar/' => array(
         '$' => 'PhabricatorCalendarBrowseController',
       ),
 
       '/drydock/' => array(
         '$' => 'DrydockResourceListController',
         'resource/$' => 'DrydockResourceListController',
         'resource/allocate/$' => 'DrydockResourceAllocateController',
         'host/' => array(
           '$' => 'DrydockHostListController',
           'edit/$' => 'DrydockHostEditController',
           'edit/(?P<id>\d+)/$' => 'DrydockhostEditController',
         ),
         'lease/$' => 'DrydockLeaseListController',
       ),
 
       '/chatlog/' => array(
         '$' =>
           'PhabricatorChatLogChannelListController',
         'channel/(?P<channel>[^/]+)/$' =>
           'PhabricatorChatLogChannelLogController',
       ),
     );
   }
 
   protected function getResourceURIMapRules() {
     return array(
       '/res/' => array(
         '(?P<package>pkg/)?(?P<hash>[a-f0-9]{8})/(?P<path>.+\.(?:css|js))$'
           => 'CelerityResourceController',
       ),
     );
   }
 
   public function buildRequest() {
     $request = new AphrontRequest($this->getHost(), $this->getPath());
     $request->setRequestData($_GET + $_POST);
     $request->setApplicationConfiguration($this);
     return $request;
   }
 
   public function handleException(Exception $ex) {
 
     // Always log the unhandled exception.
     phlog($ex);
 
     $class    = phutil_escape_html(get_class($ex));
     $message  = phutil_escape_html($ex->getMessage());
 
     if (PhabricatorEnv::getEnvConfig('phabricator.show-stack-traces')) {
       $trace = $this->renderStackTrace($ex->getTrace());
     } else {
       $trace = null;
     }
 
     $content =
       '<div class="aphront-unhandled-exception">'.
         '<div class="exception-message">'.$message.'</div>'.
         $trace.
       '</div>';
 
     $user = $this->getRequest()->getUser();
     if (!$user) {
       // If we hit an exception very early, we won't have a user.
       $user = new PhabricatorUser();
     }
 
     $dialog = new AphrontDialogView();
     $dialog
       ->setTitle('Unhandled Exception ("'.$class.'")')
       ->setClass('aphront-exception-dialog')
       ->setUser($user)
       ->appendChild($content);
 
     if ($this->getRequest()->isAjax()) {
       $dialog->addCancelButton('/', 'Close');
     }
 
     $response = new AphrontDialogResponse();
     $response->setDialog($dialog);
 
     return $response;
   }
 
   public function willSendResponse(AphrontResponse $response) {
     $request = $this->getRequest();
     $response->setRequest($request);
     if ($response instanceof AphrontDialogResponse) {
       if (!$request->isAjax()) {
         $view = new PhabricatorStandardPageView();
         $view->setRequest($request);
         $view->appendChild(
           '<div style="padding: 2em 0;">'.
             $response->buildResponseString().
           '</div>');
         $response = new AphrontWebpageResponse();
         $response->setContent($view->render());
         return $response;
       } else {
         return id(new AphrontAjaxResponse())
           ->setContent(array(
             'dialog' => $response->buildResponseString(),
           ));
       }
     } else if ($response instanceof AphrontRedirectResponse) {
       if ($request->isAjax()) {
         return id(new AphrontAjaxResponse())
           ->setContent(
             array(
               'redirect' => $response->getURI(),
             ));
       }
     }
 
     return $response;
   }
 
   public function build404Controller() {
     return array(new Phabricator404Controller($this->getRequest()), array());
   }
 
   public function buildRedirectController($uri) {
     return array(
       new PhabricatorRedirectController($this->getRequest()),
       array(
         'uri' => $uri,
       ));
   }
 
   private function renderStackTrace($trace) {
 
     $libraries = PhutilBootloader::getInstance()->getAllLibraries();
 
     // TODO: Make this configurable?
     $host = 'https://secure.phabricator.com';
 
     $browse = array(
       'arcanist' =>
         $host.'/diffusion/ARC/browse/origin:master/src/',
       'phutil' =>
         $host.'/diffusion/PHU/browse/origin:master/src/',
       'phabricator' =>
         $host.'/diffusion/P/browse/origin:master/src/',
     );
 
     $rows = array();
     $depth = count($trace);
     foreach ($trace as $part) {
       $lib = null;
       $file = idx($part, 'file');
       $relative = $file;
       foreach ($libraries as $library) {
         $root = phutil_get_library_root($library);
         if (Filesystem::isDescendant($file, $root)) {
           $lib = $library;
           $relative = Filesystem::readablePath($file, $root);
           break;
         }
       }
 
       $where = '';
       if (isset($part['class'])) {
         $where .= $part['class'].'::';
       }
       if (isset($part['function'])) {
         $where .= $part['function'].'()';
       }
 
       if ($file) {
         if (isset($browse[$lib])) {
           $file_name = phutil_render_tag(
             'a',
             array(
               'href' => $browse[$lib].$relative.'$'.$part['line'],
               'title' => $file,
               'target' => '_blank',
             ),
             phutil_escape_html($relative));
         } else {
           $file_name = phutil_render_tag(
             'span',
             array(
               'title' => $file,
             ),
             phutil_escape_html($relative));
         }
         $file_name = $file_name.' : '.(int)$part['line'];
       } else {
         $file_name = '<em>(Internal)</em>';
       }
 
 
       $rows[] = array(
         $depth--,
         phutil_escape_html($lib),
         $file_name,
         phutil_escape_html($where),
       );
     }
     $table = new AphrontTableView($rows);
     $table->setHeaders(
       array(
         'Depth',
         'Library',
         'File',
         'Where',
       ));
     $table->setColumnClasses(
       array(
         'n',
         '',
         '',
         'wide',
       ));
 
     return
       '<div class="exception-trace">'.
         '<div class="exception-trace-header">Stack Trace</div>'.
         $table->render().
       '</div>';
   }
 
 }
diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
index 40907f71fd..bd1bf2e35e 100644
--- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
+++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
@@ -1,461 +1,476 @@
 <?php
 
 /*
  * Copyright 2012 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 class DiffusionBrowseFileController extends DiffusionController {
 
   // Image types we want to display inline using <img> tags
   protected $imageTypes = array(
     'png' => 'image/png',
     'gif' => 'image/gif',
     'ico' => 'image/png',
     'jpg' => 'image/jpeg',
     'jpeg'=> 'image/jpeg'
   );
 
   // Document types that should trigger link to ?view=raw
   protected $documentTypes = array(
     'pdf'=> 'application/pdf',
     'ps' => 'application/postscript',
   );
 
   public function processRequest() {
 
     // Build the view selection form.
     $select_map = array(
       'highlighted' => 'View as Highlighted Text',
       'blame' => 'View as Highlighted Text with Blame',
       'plain' => 'View as Plain Text',
       'plainblame' => 'View as Plain Text with Blame',
       'raw' => 'View as raw document',
     );
 
     $request = $this->getRequest();
 
     $drequest = $this->getDiffusionRequest();
     $path = $drequest->getPath();
     $selected = $request->getStr('view');
     $needs_blame = ($selected == 'blame' || $selected == 'plainblame');
     $file_query = DiffusionFileContentQuery::newFromDiffusionRequest(
       $this->diffusionRequest);
     $file_query->setNeedsBlame($needs_blame);
     $file_query->loadFileContent();
     $data = $file_query->getRawData();
     if ($selected === 'raw') {
       $response = new AphrontFileResponse();
       $response->setContent($data);
       $mime_type = $this->getDocumentType($path);
       if ($mime_type) {
         $response->setMimeType($mime_type);
       } else {
         $as_filename = idx(pathinfo($path), 'basename');
         $response->setDownload($as_filename);
       }
       return $response;
     }
 
     $select = '<select name="view">';
     foreach ($select_map as $k => $v) {
       $option = phutil_render_tag(
         'option',
         array(
           'value' => $k,
           'selected' => ($k == $selected) ? 'selected' : null,
         ),
         phutil_escape_html($v));
 
       $select .= $option;
     }
     $select .= '</select>';
 
     require_celerity_resource('diffusion-source-css');
 
     $view_select_panel = new AphrontPanelView();
     $view_select_form = phutil_render_tag(
       'form',
       array(
         'action' => $request->getRequestURI(),
         'method' => 'get',
         'class'  => 'diffusion-browse-type-form',
       ),
       $select.
       '<button>View</button>');
     $view_select_panel->appendChild($view_select_form);
 
     $user = $request->getUser();
     if ($user) {
       $line = 1;
       $repository = $this->getDiffusionRequest()->getRepository();
       $editor_link = $user->loadEditorLink($path, $line, $repository);
       if ($editor_link) {
         $view_select_panel->addButton(
           phutil_render_tag(
             'a',
             array(
               'href' => $editor_link,
               'class' => 'button',
             ),
             'Edit'
           ));
       }
     }
 
     $view_select_panel->appendChild('<div style="clear: both;"></div>');
 
     // Build the content of the file.
     $corpus = $this->buildCorpus(
       $selected,
       $file_query,
       $needs_blame,
       $drequest,
       $path,
       $data
     );
 
     // Render the page.
     $content = array();
     $content[] = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'browse',
       ));
     $content[] = $view_select_panel;
     $content[] = $corpus;
     $content[] = $this->buildOpenRevisions();
 
     $nav = $this->buildSideNav('browse', true);
     $nav->appendChild($content);
 
 
     $basename = basename($this->getDiffusionRequest()->getPath());
 
     return $this->buildStandardPageResponse(
       $nav,
       array(
         'title' => $basename,
       ));
   }
 
 
   /**
    * Returns a content-type corrsponding to an image file extension
    *
    * @param string $path File path
    * @return mixed A content-type string or NULL if path doesn't end with a
    *               recognized image extension
    */
   public function getImageType($path) {
     $ext = pathinfo($path);
     $ext = idx($ext, 'extension');
     return idx($this->imageTypes, $ext);
   }
 
   /**
    * Returns a content-type corresponding to an document file extension
    *
    * @param string $path File path
    * @return mixed A content-type string or NULL if path doesn't end with a
    *               recognized document extension
    */
   public function getDocumentType($path) {
     $ext = pathinfo($path);
     $ext = idx($ext, 'extension');
     return idx($this->documentTypes, $ext);
   }
 
 
   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 buildDisplayRows($text_list, $rev_list, $blame_dict,
     $needs_blame, DiffusionRequest $drequest, $file_query, $selected) {
     $last_rev = null;
     $color = null;
     $rows = array();
     $n = 1;
     $view = $this->getRequest()->getStr('view');
 
     if ($blame_dict) {
       $epoch_list = ipull($blame_dict, 'epoch');
       $max = max($epoch_list);
       $min = min($epoch_list);
       $range = $max - $min + 1;
     } else {
       $range = 1;
     }
 
+    $targ = '';
+    $min_line = 0;
+    $line = $drequest->getLine();
+    if (strpos($line,'-') !== false) {
+      list($min,$max) = explode('-',$line,2);
+      $min_line = min($min, $max);
+      $max_line = max($min, $max);
+    } else if (strlen($line)) {
+      $min_line = $line;
+      $max_line = $line;
+    }
+
+
     foreach ($text_list as $k => $line) {
       if ($needs_blame) {
         // If the line's rev is same as the line above, show empty content
         // with same color; otherwise generate blame info. The newer a change
         // is, the darker the color.
         $rev = $rev_list[$k];
         if ($last_rev == $rev) {
           $blame_info =
             ($file_query->getSupportsBlameOnBlame() ?
               '<th style="background: '.$color.'; width: 2em;"></th>' : '').
             '<th style="background: '.$color.'; width: 9em;"></th>'.
             '<th style="background: '.$color.'"></th>';
         } else {
 
           $color_number = (int)(0xEE -
             0xEE * ($blame_dict[$rev]['epoch'] - $min) / $range);
           $color = sprintf('#%02xee%02x', $color_number, $color_number);
 
           $revision_link = self::renderRevision(
             $drequest,
             substr($rev, 0, 7));
 
           if (!$file_query->getSupportsBlameOnBlame()) {
             $prev_link = '';
           } else {
             $prev_rev = $file_query->getPrevRev($rev);
             $path = $drequest->getPath();
             $prev_link = self::renderBrowse(
               $drequest,
               $path,
               "\xC2\xAB",
               $prev_rev,
               $n,
               $selected,
               'Blame previous revision');
             $prev_link = '<th style="background: ' . $color .
               '; width: 2em;">' . $prev_link . '</th>';
           }
 
           if (isset($blame_dict[$rev]['handle'])) {
             $author_link = $blame_dict[$rev]['handle']->renderLink();
           } else {
             $author_link = phutil_escape_html($blame_dict[$rev]['author']);
           }
           $blame_info =
             $prev_link .
             '<th style="background: '.$color.
               '; width: 12em;">'.$revision_link.'</th>'.
             '<th style="background: '.$color.'; width: 12em'.
               '; font-weight: normal; color: #333;">'.$author_link.'</th>';
           $last_rev = $rev;
         }
       } else {
         $blame_info = null;
       }
 
       // Highlight the line of interest if needed.
-      if ($n == $drequest->getLine()) {
+      if ($min_line > 0 && ($n >= $min_line && $n <= $max_line)) {
         $tr = '<tr style="background: #ffff00;">';
-        $targ = '<a id="scroll_target"></a>';
-        Javelin::initBehavior('diffusion-jump-to',
-          array('target' => 'scroll_target'));
+        if ($targ == '') {
+          $targ = '<a id="scroll_target"></a>';
+          Javelin::initBehavior('diffusion-jump-to',
+            array('target' => 'scroll_target'));
+        }
       } else {
         $tr = '<tr>';
         $targ = null;
       }
 
       // Create the row display.
       $uri_path = $drequest->getUriPath();
       $uri_rev  = $drequest->getStableCommitName();
       $uri_view = $view
         ? '?view='.$view
         : null;
 
       $l = phutil_render_tag(
         'a',
         array(
           'class' => 'diffusion-line-link',
           'href' => $uri_path.';'.$uri_rev.'$'.$n.$uri_view,
         ),
         $n);
 
       $rows[] = $tr.$blame_info.'<th>'.$l.'</th><td>'.$targ.$line.'</td></tr>';
       ++$n;
     }
 
     return $rows;
   }
 
 
   private static function renderRevision(DiffusionRequest $drequest,
     $revision) {
 
     $callsign = $drequest->getCallsign();
 
     $name = 'r'.$callsign.$revision;
     return phutil_render_tag(
       'a',
       array(
            'href' => '/'.$name,
       ),
       $name
     );
   }
 
 
   private static function renderBrowse(
     DiffusionRequest $drequest,
     $path,
     $name = null,
     $rev = null,
     $line = null,
     $view = null,
     $title = null) {
 
     $callsign = $drequest->getCallsign();
 
     if ($name === null) {
       $name = $path;
     }
 
     $at = null;
     if ($rev) {
       $at = ';'.$rev;
     }
 
     if ($view) {
       $view = '?view='.$view;
     }
 
     if ($line) {
       $line = '$'.$line;
     }
 
     return phutil_render_tag(
       'a',
       array(
         'href' => "/diffusion/{$callsign}/browse/{$path}{$at}{$line}{$view}",
         'title' => $title,
       ),
       $name
     );
   }
 
 
 }