@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge and modernize Browse controllers in Diffusion

Summary:
Ref T4245. Browsing is huge and currently split across 5 files using controller delegation.

Although having a huge file isn't great, I think the way it is split up is currently worse, and it gets weird with more flexible repository identifiers.

So this is mostly merging five controllers into one, then a bit of modernization.

I think this can probably be split up better by pulling some of it out into views, instead of using delegation.

Test Plan: Browsed files, directories, and search results.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T4245

Differential Revision: https://secure.phabricator.com/D14942

+1480 -1524
-8
src/__phutil_library_map__.php
··· 533 533 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', 534 534 'DiffusionBranchTableView' => 'applications/diffusion/view/DiffusionBranchTableView.php', 535 535 'DiffusionBrowseController' => 'applications/diffusion/controller/DiffusionBrowseController.php', 536 - 'DiffusionBrowseDirectoryController' => 'applications/diffusion/controller/DiffusionBrowseDirectoryController.php', 537 - 'DiffusionBrowseFileController' => 'applications/diffusion/controller/DiffusionBrowseFileController.php', 538 - 'DiffusionBrowseMainController' => 'applications/diffusion/controller/DiffusionBrowseMainController.php', 539 536 'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php', 540 537 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', 541 - 'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php', 542 538 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', 543 539 'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php', 544 540 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', ··· 4501 4497 'DiffusionBranchTableController' => 'DiffusionController', 4502 4498 'DiffusionBranchTableView' => 'DiffusionView', 4503 4499 'DiffusionBrowseController' => 'DiffusionController', 4504 - 'DiffusionBrowseDirectoryController' => 'DiffusionBrowseController', 4505 - 'DiffusionBrowseFileController' => 'DiffusionBrowseController', 4506 - 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 4507 4500 'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 4508 4501 'DiffusionBrowseResultSet' => 'Phobject', 4509 - 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 4510 4502 'DiffusionBrowseTableView' => 'DiffusionView', 4511 4503 'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery', 4512 4504 'DiffusionChangeController' => 'DiffusionController',
+1 -1
src/applications/diffusion/application/PhabricatorDiffusionApplication.php
··· 70 70 'repository/(?P<dblob>.*)' => 'DiffusionRepositoryController', 71 71 'change/(?P<dblob>.*)' => 'DiffusionChangeController', 72 72 'history/(?P<dblob>.*)' => 'DiffusionHistoryController', 73 - 'browse/(?P<dblob>.*)' => 'DiffusionBrowseMainController', 73 + 'browse/(?P<dblob>.*)' => 'DiffusionBrowseController', 74 74 'lastmodified/(?P<dblob>.*)' => 'DiffusionLastModifiedController', 75 75 'diff/' => 'DiffusionDiffController', 76 76 'tags/(?P<dblob>.*)' => 'DiffusionTagListController',
+1479 -5
src/applications/diffusion/controller/DiffusionBrowseController.php
··· 1 1 <?php 2 2 3 - abstract class DiffusionBrowseController extends DiffusionController { 3 + final class DiffusionBrowseController extends DiffusionController { 4 + 5 + private $lintCommit; 6 + private $lintMessages; 7 + private $coverage; 4 8 5 9 public function shouldAllowPublic() { 6 10 return true; 7 11 } 8 12 13 + public function handleRequest(AphrontRequest $request) { 14 + $response = $this->loadDiffusionContext(); 15 + if ($response) { 16 + return $response; 17 + } 18 + 19 + $drequest = $this->getDiffusionRequest(); 20 + 21 + // Figure out if we're browsing a directory, a file, or a search result 22 + // list. 23 + 24 + $grep = $request->getStr('grep'); 25 + $find = $request->getStr('find'); 26 + if (strlen($grep) || strlen($find)) { 27 + return $this->browseSearch(); 28 + } 29 + 30 + $results = DiffusionBrowseResultSet::newFromConduit( 31 + $this->callConduitWithDiffusionRequest( 32 + 'diffusion.browsequery', 33 + array( 34 + 'path' => $drequest->getPath(), 35 + 'commit' => $drequest->getStableCommit(), 36 + ))); 37 + $reason = $results->getReasonForEmptyResultSet(); 38 + $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); 39 + 40 + if ($is_file) { 41 + return $this->browseFile($results); 42 + } else { 43 + return $this->browseDirectory($results); 44 + } 45 + } 46 + 47 + private function browseSearch() { 48 + $drequest = $this->getDiffusionRequest(); 49 + 50 + $actions = $this->buildActionView($drequest); 51 + $properties = $this->buildPropertyView($drequest, $actions); 52 + 53 + $object_box = id(new PHUIObjectBoxView()) 54 + ->setHeader($this->buildHeaderView($drequest)) 55 + ->addPropertyList($properties); 56 + 57 + $content = array(); 58 + 59 + $content[] = $object_box; 60 + $content[] = $this->renderSearchForm($collapsed = false); 61 + $content[] = $this->renderSearchResults(); 62 + 63 + $crumbs = $this->buildCrumbs( 64 + array( 65 + 'branch' => true, 66 + 'path' => true, 67 + 'view' => 'browse', 68 + )); 69 + 70 + return $this->newPage() 71 + ->setTitle( 72 + array( 73 + nonempty(basename($drequest->getPath()), '/'), 74 + $drequest->getRepository()->getDisplayName(), 75 + )) 76 + ->setCrumbs($crumbs) 77 + ->appendChild($content); 78 + } 79 + 80 + private function browseFile() { 81 + $viewer = $this->getViewer(); 82 + $request = $this->getRequest(); 83 + $drequest = $this->getDiffusionRequest(); 84 + $repository = $drequest->getRepository(); 85 + 86 + $before = $request->getStr('before'); 87 + if ($before) { 88 + return $this->buildBeforeResponse($before); 89 + } 90 + 91 + $path = $drequest->getPath(); 92 + 93 + $preferences = $viewer->loadPreferences(); 94 + 95 + $show_blame = $request->getBool( 96 + 'blame', 97 + $preferences->getPreference( 98 + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, 99 + false)); 100 + $show_color = $request->getBool( 101 + 'color', 102 + $preferences->getPreference( 103 + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, 104 + true)); 105 + 106 + $view = $request->getStr('view'); 107 + if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { 108 + $preferences->setPreference( 109 + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, 110 + $show_blame); 111 + $preferences->setPreference( 112 + PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, 113 + $show_color); 114 + $preferences->save(); 115 + 116 + $uri = $request->getRequestURI() 117 + ->alter('blame', null) 118 + ->alter('color', null); 119 + 120 + return id(new AphrontRedirectResponse())->setURI($uri); 121 + } 122 + 123 + // We need the blame information if blame is on and we're building plain 124 + // text, or blame is on and this is an Ajax request. If blame is on and 125 + // this is a colorized request, we don't show blame at first (we ajax it 126 + // in afterward) so we don't need to query for it. 127 + $needs_blame = ($show_blame && !$show_color) || 128 + ($show_blame && $request->isAjax()); 129 + 130 + $params = array( 131 + 'commit' => $drequest->getCommit(), 132 + 'path' => $drequest->getPath(), 133 + 'needsBlame' => $needs_blame, 134 + ); 135 + 136 + $byte_limit = null; 137 + if ($view !== 'raw') { 138 + $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); 139 + $time_limit = 10; 140 + 141 + $params += array( 142 + 'timeout' => $time_limit, 143 + 'byteLimit' => $byte_limit, 144 + ); 145 + } 146 + 147 + $file_content = DiffusionFileContent::newFromConduit( 148 + $this->callConduitWithDiffusionRequest( 149 + 'diffusion.filecontentquery', 150 + $params)); 151 + $data = $file_content->getCorpus(); 152 + 153 + if ($view === 'raw') { 154 + return $this->buildRawResponse($path, $data); 155 + } 156 + 157 + $this->loadLintMessages(); 158 + $this->coverage = $drequest->loadCoverage(); 159 + 160 + if ($byte_limit && (strlen($data) == $byte_limit)) { 161 + $corpus = $this->buildErrorCorpus( 162 + pht( 163 + 'This file is larger than %s byte(s), and too large to display '. 164 + 'in the web UI.', 165 + $byte_limit)); 166 + } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { 167 + $file = $this->loadFileForData($path, $data); 168 + $file_uri = $file->getBestURI(); 169 + 170 + if ($file->isViewableImage()) { 171 + $corpus = $this->buildImageCorpus($file_uri); 172 + } else { 173 + $corpus = $this->buildBinaryCorpus($file_uri, $data); 174 + } 175 + } else { 176 + // Build the content of the file. 177 + $corpus = $this->buildCorpus( 178 + $show_blame, 179 + $show_color, 180 + $file_content, 181 + $needs_blame, 182 + $drequest, 183 + $path, 184 + $data); 185 + } 186 + 187 + if ($request->isAjax()) { 188 + return id(new AphrontAjaxResponse())->setContent($corpus); 189 + } 190 + 191 + require_celerity_resource('diffusion-source-css'); 192 + 193 + // Render the page. 194 + $view = $this->buildActionView($drequest); 195 + $action_list = $this->enrichActionView( 196 + $view, 197 + $drequest, 198 + $show_blame, 199 + $show_color); 200 + 201 + $properties = $this->buildPropertyView($drequest, $action_list); 202 + $object_box = id(new PHUIObjectBoxView()) 203 + ->setHeader($this->buildHeaderView($drequest)) 204 + ->addPropertyList($properties); 205 + 206 + $content = array(); 207 + $content[] = $object_box; 208 + 209 + $follow = $request->getStr('follow'); 210 + if ($follow) { 211 + $notice = new PHUIInfoView(); 212 + $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); 213 + $notice->setTitle(pht('Unable to Continue')); 214 + switch ($follow) { 215 + case 'first': 216 + $notice->appendChild( 217 + pht( 218 + 'Unable to continue tracing the history of this file because '. 219 + 'this commit is the first commit in the repository.')); 220 + break; 221 + case 'created': 222 + $notice->appendChild( 223 + pht( 224 + 'Unable to continue tracing the history of this file because '. 225 + 'this commit created the file.')); 226 + break; 227 + } 228 + $content[] = $notice; 229 + } 230 + 231 + $renamed = $request->getStr('renamed'); 232 + if ($renamed) { 233 + $notice = new PHUIInfoView(); 234 + $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); 235 + $notice->setTitle(pht('File Renamed')); 236 + $notice->appendChild( 237 + pht( 238 + 'File history passes through a rename from "%s" to "%s".', 239 + $drequest->getPath(), 240 + $renamed)); 241 + $content[] = $notice; 242 + } 243 + 244 + $content[] = $corpus; 245 + $content[] = $this->buildOpenRevisions(); 246 + 247 + $crumbs = $this->buildCrumbs( 248 + array( 249 + 'branch' => true, 250 + 'path' => true, 251 + 'view' => 'browse', 252 + )); 253 + 254 + $basename = basename($this->getDiffusionRequest()->getPath()); 255 + 256 + return $this->newPage() 257 + ->setTitle( 258 + array( 259 + $basename, 260 + $repository->getDisplayName(), 261 + )) 262 + ->setCrumbs($crumbs) 263 + ->appendChild($content); 264 + } 265 + 266 + public function browseDirectory(DiffusionBrowseResultSet $results) { 267 + $request = $this->getRequest(); 268 + $drequest = $this->getDiffusionRequest(); 269 + $repository = $drequest->getRepository(); 270 + 271 + $reason = $results->getReasonForEmptyResultSet(); 272 + 273 + $content = array(); 274 + $actions = $this->buildActionView($drequest); 275 + $properties = $this->buildPropertyView($drequest, $actions); 276 + 277 + $object_box = id(new PHUIObjectBoxView()) 278 + ->setHeader($this->buildHeaderView($drequest)) 279 + ->addPropertyList($properties); 280 + 281 + $content[] = $object_box; 282 + $content[] = $this->renderSearchForm($collapsed = true); 283 + 284 + if (!$results->isValidResults()) { 285 + $empty_result = new DiffusionEmptyResultView(); 286 + $empty_result->setDiffusionRequest($drequest); 287 + $empty_result->setDiffusionBrowseResultSet($results); 288 + $empty_result->setView($request->getStr('view')); 289 + $content[] = $empty_result; 290 + } else { 291 + $phids = array(); 292 + foreach ($results->getPaths() as $result) { 293 + $data = $result->getLastCommitData(); 294 + if ($data) { 295 + if ($data->getCommitDetail('authorPHID')) { 296 + $phids[$data->getCommitDetail('authorPHID')] = true; 297 + } 298 + } 299 + } 300 + 301 + $phids = array_keys($phids); 302 + $handles = $this->loadViewerHandles($phids); 303 + 304 + $browse_table = new DiffusionBrowseTableView(); 305 + $browse_table->setDiffusionRequest($drequest); 306 + $browse_table->setHandles($handles); 307 + $browse_table->setPaths($results->getPaths()); 308 + $browse_table->setUser($request->getUser()); 309 + 310 + $browse_panel = new PHUIObjectBoxView(); 311 + $browse_panel->setHeaderText($drequest->getPath(), '/'); 312 + $browse_panel->setTable($browse_table); 313 + 314 + $content[] = $browse_panel; 315 + } 316 + 317 + $content[] = $this->buildOpenRevisions(); 318 + 319 + 320 + $readme_path = $results->getReadmePath(); 321 + if ($readme_path) { 322 + $readme_content = $this->callConduitWithDiffusionRequest( 323 + 'diffusion.filecontentquery', 324 + array( 325 + 'path' => $readme_path, 326 + 'commit' => $drequest->getStableCommit(), 327 + )); 328 + if ($readme_content) { 329 + $content[] = id(new DiffusionReadmeView()) 330 + ->setUser($this->getViewer()) 331 + ->setPath($readme_path) 332 + ->setContent($readme_content['corpus']); 333 + } 334 + } 335 + 336 + $crumbs = $this->buildCrumbs( 337 + array( 338 + 'branch' => true, 339 + 'path' => true, 340 + 'view' => 'browse', 341 + )); 342 + 343 + return $this->newPage() 344 + ->setTitle( 345 + array( 346 + nonempty(basename($drequest->getPath()), '/'), 347 + $repository->getDisplayName(), 348 + )) 349 + ->setCrumbs($crumbs) 350 + ->appendChild($content); 351 + } 352 + 353 + private function renderSearchResults() { 354 + $drequest = $this->getDiffusionRequest(); 355 + $repository = $drequest->getRepository(); 356 + $results = array(); 357 + 358 + $limit = 100; 359 + $page = $this->getRequest()->getInt('page', 0); 360 + $pager = new PHUIPagerView(); 361 + $pager->setPageSize($limit); 362 + $pager->setOffset($page); 363 + $pager->setURI($this->getRequest()->getRequestURI(), 'page'); 364 + 365 + $search_mode = null; 366 + 367 + switch ($repository->getVersionControlSystem()) { 368 + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 369 + $results = array(); 370 + break; 371 + default: 372 + if (strlen($this->getRequest()->getStr('grep'))) { 373 + $search_mode = 'grep'; 374 + $query_string = $this->getRequest()->getStr('grep'); 375 + $results = $this->callConduitWithDiffusionRequest( 376 + 'diffusion.searchquery', 377 + array( 378 + 'grep' => $query_string, 379 + 'commit' => $drequest->getStableCommit(), 380 + 'path' => $drequest->getPath(), 381 + 'limit' => $limit + 1, 382 + 'offset' => $page, 383 + )); 384 + } else { // Filename search. 385 + $search_mode = 'find'; 386 + $query_string = $this->getRequest()->getStr('find'); 387 + $results = $this->callConduitWithDiffusionRequest( 388 + 'diffusion.querypaths', 389 + array( 390 + 'pattern' => $query_string, 391 + 'commit' => $drequest->getStableCommit(), 392 + 'path' => $drequest->getPath(), 393 + 'limit' => $limit + 1, 394 + 'offset' => $page, 395 + )); 396 + } 397 + break; 398 + } 399 + 400 + $results = $pager->sliceResults($results); 401 + 402 + if ($search_mode == 'grep') { 403 + $table = $this->renderGrepResults($results, $query_string); 404 + $header = pht( 405 + 'File content matching "%s" under "%s"', 406 + $query_string, 407 + nonempty($drequest->getPath(), '/')); 408 + } else { 409 + $table = $this->renderFindResults($results); 410 + $header = pht( 411 + 'Paths matching "%s" under "%s"', 412 + $query_string, 413 + nonempty($drequest->getPath(), '/')); 414 + } 415 + 416 + $box = id(new PHUIObjectBoxView()) 417 + ->setHeaderText($header) 418 + ->setTable($table); 419 + 420 + $pager_box = $this->renderTablePagerBox($pager); 421 + 422 + return array($box, $pager_box); 423 + } 424 + 425 + private function renderGrepResults(array $results, $pattern) { 426 + $drequest = $this->getDiffusionRequest(); 427 + 428 + require_celerity_resource('phabricator-search-results-css'); 429 + 430 + $rows = array(); 431 + foreach ($results as $result) { 432 + list($path, $line, $string) = $result; 433 + 434 + $href = $drequest->generateURI(array( 435 + 'action' => 'browse', 436 + 'path' => $path, 437 + 'line' => $line, 438 + )); 439 + 440 + $matches = null; 441 + $count = @preg_match_all( 442 + '('.$pattern.')u', 443 + $string, 444 + $matches, 445 + PREG_OFFSET_CAPTURE); 446 + 447 + if (!$count) { 448 + $output = ltrim($string); 449 + } else { 450 + $output = array(); 451 + $cursor = 0; 452 + $length = strlen($string); 453 + foreach ($matches[0] as $match) { 454 + $offset = $match[1]; 455 + if ($cursor != $offset) { 456 + $output[] = array( 457 + 'text' => substr($string, $cursor, $offset), 458 + 'highlight' => false, 459 + ); 460 + } 461 + $output[] = array( 462 + 'text' => $match[0], 463 + 'highlight' => true, 464 + ); 465 + $cursor = $offset + strlen($match[0]); 466 + } 467 + if ($cursor != $length) { 468 + $output[] = array( 469 + 'text' => substr($string, $cursor), 470 + 'highlight' => false, 471 + ); 472 + } 473 + 474 + if ($output) { 475 + $output[0]['text'] = ltrim($output[0]['text']); 476 + } 477 + 478 + foreach ($output as $key => $segment) { 479 + if ($segment['highlight']) { 480 + $output[$key] = phutil_tag('strong', array(), $segment['text']); 481 + } else { 482 + $output[$key] = $segment['text']; 483 + } 484 + } 485 + } 486 + 487 + $string = phutil_tag( 488 + 'pre', 489 + array('class' => 'PhabricatorMonospaced phui-source-fragment'), 490 + $output); 491 + 492 + $path = Filesystem::readablePath($path, $drequest->getPath()); 493 + 494 + $rows[] = array( 495 + phutil_tag('a', array('href' => $href), $path), 496 + $line, 497 + $string, 498 + ); 499 + } 500 + 501 + $table = id(new AphrontTableView($rows)) 502 + ->setClassName('remarkup-code') 503 + ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) 504 + ->setColumnClasses(array('', 'n', 'wide')) 505 + ->setNoDataString( 506 + pht( 507 + 'The pattern you searched for was not found in the content of any '. 508 + 'files.')); 509 + 510 + return $table; 511 + } 512 + 513 + private function renderFindResults(array $results) { 514 + $drequest = $this->getDiffusionRequest(); 515 + 516 + $rows = array(); 517 + foreach ($results as $result) { 518 + $href = $drequest->generateURI(array( 519 + 'action' => 'browse', 520 + 'path' => $result, 521 + )); 522 + 523 + $readable = Filesystem::readablePath($result, $drequest->getPath()); 524 + 525 + $rows[] = array( 526 + phutil_tag('a', array('href' => $href), $readable), 527 + ); 528 + } 529 + 530 + $table = id(new AphrontTableView($rows)) 531 + ->setHeaders(array(pht('Path'))) 532 + ->setColumnClasses(array('wide')) 533 + ->setNoDataString( 534 + pht( 535 + 'The pattern you searched for did not match the names of any '. 536 + 'files.')); 537 + 538 + return $table; 539 + } 540 + 541 + private function loadLintMessages() { 542 + $drequest = $this->getDiffusionRequest(); 543 + $branch = $drequest->loadBranch(); 544 + 545 + if (!$branch || !$branch->getLintCommit()) { 546 + return; 547 + } 548 + 549 + $this->lintCommit = $branch->getLintCommit(); 550 + 551 + $conn = id(new PhabricatorRepository())->establishConnection('r'); 552 + 553 + $where = ''; 554 + if ($drequest->getLint()) { 555 + $where = qsprintf( 556 + $conn, 557 + 'AND code = %s', 558 + $drequest->getLint()); 559 + } 560 + 561 + $this->lintMessages = queryfx_all( 562 + $conn, 563 + 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', 564 + PhabricatorRepository::TABLE_LINTMESSAGE, 565 + $branch->getID(), 566 + $where, 567 + '/'.$drequest->getPath()); 568 + } 569 + 570 + private function buildCorpus( 571 + $show_blame, 572 + $show_color, 573 + DiffusionFileContent $file_content, 574 + $needs_blame, 575 + DiffusionRequest $drequest, 576 + $path, 577 + $data) { 578 + 579 + if (!$show_color) { 580 + $style = 581 + 'border: none; width: 100%; height: 80em; font-family: monospace'; 582 + if (!$show_blame) { 583 + $corpus = phutil_tag( 584 + 'textarea', 585 + array( 586 + 'style' => $style, 587 + ), 588 + $file_content->getCorpus()); 589 + } else { 590 + $text_list = $file_content->getTextList(); 591 + $rev_list = $file_content->getRevList(); 592 + $blame_dict = $file_content->getBlameDict(); 593 + 594 + $rows = array(); 595 + foreach ($text_list as $k => $line) { 596 + $rev = $rev_list[$k]; 597 + $author = $blame_dict[$rev]['author']; 598 + $rows[] = 599 + sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); 600 + } 601 + 602 + $corpus = phutil_tag( 603 + 'textarea', 604 + array( 605 + 'style' => $style, 606 + ), 607 + implode("\n", $rows)); 608 + } 609 + } else { 610 + require_celerity_resource('syntax-highlighting-css'); 611 + $text_list = $file_content->getTextList(); 612 + $rev_list = $file_content->getRevList(); 613 + $blame_dict = $file_content->getBlameDict(); 614 + 615 + $text_list = implode("\n", $text_list); 616 + $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( 617 + $path, 618 + $text_list); 619 + $text_list = explode("\n", $text_list); 620 + 621 + $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, 622 + $needs_blame, $drequest, $show_blame, $show_color); 623 + 624 + $corpus_table = javelin_tag( 625 + 'table', 626 + array( 627 + 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', 628 + 'sigil' => 'phabricator-source', 629 + ), 630 + $rows); 631 + 632 + if ($this->getRequest()->isAjax()) { 633 + return $corpus_table; 634 + } 635 + 636 + $id = celerity_generate_unique_node_id(); 637 + 638 + $repo = $drequest->getRepository(); 639 + $symbol_repos = nonempty($repo->getSymbolSources(), array()); 640 + $symbol_repos[] = $repo->getPHID(); 641 + 642 + $lang = last(explode('.', $drequest->getPath())); 643 + $repo_languages = $repo->getSymbolLanguages(); 644 + $repo_languages = nonempty($repo_languages, array()); 645 + $repo_languages = array_fill_keys($repo_languages, true); 646 + 647 + $needs_symbols = true; 648 + if ($repo_languages && $symbol_repos) { 649 + $have_symbols = id(new DiffusionSymbolQuery()) 650 + ->existsSymbolsInRepository($repo->getPHID()); 651 + if (!$have_symbols) { 652 + $needs_symbols = false; 653 + } 654 + } 655 + 656 + if ($needs_symbols && $repo_languages) { 657 + $needs_symbols = isset($repo_languages[$lang]); 658 + } 659 + 660 + if ($needs_symbols) { 661 + Javelin::initBehavior( 662 + 'repository-crossreference', 663 + array( 664 + 'container' => $id, 665 + 'lang' => $lang, 666 + 'repositories' => $symbol_repos, 667 + )); 668 + } 669 + 670 + $corpus = phutil_tag( 671 + 'div', 672 + array( 673 + 'id' => $id, 674 + ), 675 + $corpus_table); 676 + 677 + Javelin::initBehavior('load-blame', array('id' => $id)); 678 + } 679 + 680 + $edit = $this->renderEditButton(); 681 + $file = $this->renderFileButton(); 682 + $header = id(new PHUIHeaderView()) 683 + ->setHeader(pht('File Contents')) 684 + ->addActionLink($edit) 685 + ->addActionLink($file); 686 + 687 + $corpus = id(new PHUIObjectBoxView()) 688 + ->setHeader($header) 689 + ->appendChild($corpus) 690 + ->setCollapsed(true); 691 + 692 + return $corpus; 693 + } 694 + 695 + private function enrichActionView( 696 + PhabricatorActionListView $view, 697 + DiffusionRequest $drequest, 698 + $show_blame, 699 + $show_color) { 700 + 701 + $viewer = $this->getRequest()->getUser(); 702 + $base_uri = $this->getRequest()->getRequestURI(); 703 + 704 + $view->addAction( 705 + id(new PhabricatorActionView()) 706 + ->setName(pht('Show Last Change')) 707 + ->setHref( 708 + $drequest->generateURI( 709 + array( 710 + 'action' => 'change', 711 + ))) 712 + ->setIcon('fa-backward')); 713 + 714 + if ($show_blame) { 715 + $blame_text = pht('Disable Blame'); 716 + $blame_icon = 'fa-exclamation-circle lightgreytext'; 717 + $blame_value = 0; 718 + } else { 719 + $blame_text = pht('Enable Blame'); 720 + $blame_icon = 'fa-exclamation-circle'; 721 + $blame_value = 1; 722 + } 723 + 724 + $view->addAction( 725 + id(new PhabricatorActionView()) 726 + ->setName($blame_text) 727 + ->setHref($base_uri->alter('blame', $blame_value)) 728 + ->setIcon($blame_icon) 729 + ->setUser($viewer) 730 + ->setRenderAsForm($viewer->isLoggedIn())); 731 + 732 + if ($show_color) { 733 + $highlight_text = pht('Disable Highlighting'); 734 + $highlight_icon = 'fa-star-o grey'; 735 + $highlight_value = 0; 736 + } else { 737 + $highlight_text = pht('Enable Highlighting'); 738 + $highlight_icon = 'fa-star'; 739 + $highlight_value = 1; 740 + } 741 + 742 + $view->addAction( 743 + id(new PhabricatorActionView()) 744 + ->setName($highlight_text) 745 + ->setHref($base_uri->alter('color', $highlight_value)) 746 + ->setIcon($highlight_icon) 747 + ->setUser($viewer) 748 + ->setRenderAsForm($viewer->isLoggedIn())); 749 + 750 + $href = null; 751 + if ($this->getRequest()->getStr('lint') !== null) { 752 + $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); 753 + $href = $base_uri->alter('lint', null); 754 + 755 + } else if ($this->lintCommit === null) { 756 + $lint_text = pht('Lint not Available'); 757 + } else { 758 + $lint_text = pht( 759 + 'Show %d Lint Message(s)', 760 + count($this->lintMessages)); 761 + $href = $this->getDiffusionRequest()->generateURI(array( 762 + 'action' => 'browse', 763 + 'commit' => $this->lintCommit, 764 + ))->alter('lint', ''); 765 + } 766 + 767 + $view->addAction( 768 + id(new PhabricatorActionView()) 769 + ->setName($lint_text) 770 + ->setHref($href) 771 + ->setIcon('fa-exclamation-triangle') 772 + ->setDisabled(!$href)); 773 + 774 + return $view; 775 + } 776 + 777 + private function renderEditButton() { 778 + $request = $this->getRequest(); 779 + $user = $request->getUser(); 780 + 781 + $drequest = $this->getDiffusionRequest(); 782 + 783 + $repository = $drequest->getRepository(); 784 + $path = $drequest->getPath(); 785 + $line = nonempty((int)$drequest->getLine(), 1); 786 + 787 + $editor_link = $user->loadEditorLink($path, $line, $repository); 788 + $template = $user->loadEditorLink($path, '%l', $repository); 789 + 790 + $icon_edit = id(new PHUIIconView()) 791 + ->setIconFont('fa-pencil'); 792 + $button = id(new PHUIButtonView()) 793 + ->setTag('a') 794 + ->setText(pht('Open in Editor')) 795 + ->setHref($editor_link) 796 + ->setIcon($icon_edit) 797 + ->setID('editor_link') 798 + ->setMetadata(array('link_template' => $template)) 799 + ->setDisabled(!$editor_link); 800 + 801 + return $button; 802 + } 803 + 804 + private function renderFileButton($file_uri = null) { 805 + 806 + $base_uri = $this->getRequest()->getRequestURI(); 807 + 808 + if ($file_uri) { 809 + $text = pht('Download Raw File'); 810 + $href = $file_uri; 811 + $icon = 'fa-download'; 812 + } else { 813 + $text = pht('View Raw File'); 814 + $href = $base_uri->alter('view', 'raw'); 815 + $icon = 'fa-file-text'; 816 + } 817 + 818 + $iconview = id(new PHUIIconView()) 819 + ->setIconFont($icon); 820 + $button = id(new PHUIButtonView()) 821 + ->setTag('a') 822 + ->setText($text) 823 + ->setHref($href) 824 + ->setIcon($iconview); 825 + 826 + return $button; 827 + } 828 + 829 + 830 + private function buildDisplayRows( 831 + array $text_list, 832 + array $rev_list, 833 + array $blame_dict, 834 + $needs_blame, 835 + DiffusionRequest $drequest, 836 + $show_blame, 837 + $show_color) { 838 + 839 + $handles = array(); 840 + if ($blame_dict) { 841 + $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); 842 + $epoch_min = min($epoch_list); 843 + $epoch_max = max($epoch_list); 844 + $epoch_range = ($epoch_max - $epoch_min) + 1; 845 + 846 + $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); 847 + $handles = $this->loadViewerHandles($author_phids); 848 + } 849 + 850 + $line_arr = array(); 851 + $line_str = $drequest->getLine(); 852 + $ranges = explode(',', $line_str); 853 + foreach ($ranges as $range) { 854 + if (strpos($range, '-') !== false) { 855 + list($min, $max) = explode('-', $range, 2); 856 + $line_arr[] = array( 857 + 'min' => min($min, $max), 858 + 'max' => max($min, $max), 859 + ); 860 + } else if (strlen($range)) { 861 + $line_arr[] = array( 862 + 'min' => $range, 863 + 'max' => $range, 864 + ); 865 + } 866 + } 867 + 868 + $display = array(); 869 + 870 + $line_number = 1; 871 + $last_rev = null; 872 + $color = null; 873 + foreach ($text_list as $k => $line) { 874 + $display_line = array( 875 + 'epoch' => null, 876 + 'commit' => null, 877 + 'author' => null, 878 + 'target' => null, 879 + 'highlighted' => null, 880 + 'line' => $line_number, 881 + 'data' => $line, 882 + ); 883 + 884 + if ($show_blame) { 885 + // If the line's rev is same as the line above, show empty content 886 + // with same color; otherwise generate blame info. The newer a change 887 + // is, the more saturated the color. 888 + 889 + $rev = idx($rev_list, $k, $last_rev); 890 + 891 + if ($last_rev == $rev) { 892 + $display_line['color'] = $color; 893 + } else { 894 + $blame = $blame_dict[$rev]; 895 + 896 + if (!isset($blame['epoch'])) { 897 + $color = '#ffd'; // Render as warning. 898 + } else { 899 + $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; 900 + $color_value = 0xE6 * (1.0 - $color_ratio); 901 + $color = sprintf( 902 + '#%02x%02x%02x', 903 + $color_value, 904 + 0xF6, 905 + $color_value); 906 + } 907 + 908 + $display_line['epoch'] = idx($blame, 'epoch'); 909 + $display_line['color'] = $color; 910 + $display_line['commit'] = $rev; 911 + 912 + $author_phid = idx($blame, 'authorPHID'); 913 + if ($author_phid && $handles[$author_phid]) { 914 + $author_link = $handles[$author_phid]->renderLink(); 915 + } else { 916 + $author_link = $blame['author']; 917 + } 918 + $display_line['author'] = $author_link; 919 + 920 + $last_rev = $rev; 921 + } 922 + } 923 + 924 + if ($line_arr) { 925 + if ($line_number == $line_arr[0]['min']) { 926 + $display_line['target'] = true; 927 + } 928 + foreach ($line_arr as $range) { 929 + if ($line_number >= $range['min'] && 930 + $line_number <= $range['max']) { 931 + $display_line['highlighted'] = true; 932 + } 933 + } 934 + } 935 + 936 + $display[] = $display_line; 937 + ++$line_number; 938 + } 939 + 940 + $request = $this->getRequest(); 941 + $viewer = $request->getUser(); 942 + 943 + $commits = array_filter(ipull($display, 'commit')); 944 + if ($commits) { 945 + $commits = id(new DiffusionCommitQuery()) 946 + ->setViewer($viewer) 947 + ->withRepository($drequest->getRepository()) 948 + ->withIdentifiers($commits) 949 + ->execute(); 950 + $commits = mpull($commits, null, 'getCommitIdentifier'); 951 + } 952 + 953 + $revision_ids = id(new DifferentialRevision()) 954 + ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); 955 + $revisions = array(); 956 + if ($revision_ids) { 957 + $revisions = id(new DifferentialRevisionQuery()) 958 + ->setViewer($viewer) 959 + ->withIDs($revision_ids) 960 + ->execute(); 961 + } 962 + 963 + $phids = array(); 964 + foreach ($commits as $commit) { 965 + if ($commit->getAuthorPHID()) { 966 + $phids[] = $commit->getAuthorPHID(); 967 + } 968 + } 969 + foreach ($revisions as $revision) { 970 + if ($revision->getAuthorPHID()) { 971 + $phids[] = $revision->getAuthorPHID(); 972 + } 973 + } 974 + $handles = $this->loadViewerHandles($phids); 975 + 976 + Javelin::initBehavior('phabricator-oncopy', array()); 977 + 978 + $engine = null; 979 + $inlines = array(); 980 + if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { 981 + $engine = new PhabricatorMarkupEngine(); 982 + $engine->setViewer($viewer); 983 + 984 + foreach ($this->lintMessages as $message) { 985 + $inline = id(new PhabricatorAuditInlineComment()) 986 + ->setSyntheticAuthor( 987 + ArcanistLintSeverity::getStringForSeverity($message['severity']). 988 + ' '.$message['code'].' ('.$message['name'].')') 989 + ->setLineNumber($message['line']) 990 + ->setContent($message['description']); 991 + $inlines[$message['line']][] = $inline; 992 + 993 + $engine->addObject( 994 + $inline, 995 + PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 996 + } 997 + 998 + $engine->process(); 999 + require_celerity_resource('differential-changeset-view-css'); 1000 + } 1001 + 1002 + $rows = $this->renderInlines( 1003 + idx($inlines, 0, array()), 1004 + $show_blame, 1005 + (bool)$this->coverage, 1006 + $engine); 1007 + 1008 + foreach ($display as $line) { 1009 + 1010 + $line_href = $drequest->generateURI( 1011 + array( 1012 + 'action' => 'browse', 1013 + 'line' => $line['line'], 1014 + 'stable' => true, 1015 + )); 1016 + 1017 + $blame = array(); 1018 + $style = null; 1019 + if (array_key_exists('color', $line)) { 1020 + if ($line['color']) { 1021 + $style = 'background: '.$line['color'].';'; 1022 + } 1023 + 1024 + $before_link = null; 1025 + $commit_link = null; 1026 + $revision_link = null; 1027 + if (idx($line, 'commit')) { 1028 + $commit = $line['commit']; 1029 + 1030 + if (idx($commits, $commit)) { 1031 + $tooltip = $this->renderCommitTooltip( 1032 + $commits[$commit], 1033 + $handles, 1034 + $line['author']); 1035 + } else { 1036 + $tooltip = null; 1037 + } 1038 + 1039 + Javelin::initBehavior('phabricator-tooltips', array()); 1040 + require_celerity_resource('aphront-tooltip-css'); 1041 + 1042 + $commit_link = javelin_tag( 1043 + 'a', 1044 + array( 1045 + 'href' => $drequest->generateURI( 1046 + array( 1047 + 'action' => 'commit', 1048 + 'commit' => $line['commit'], 1049 + )), 1050 + 'sigil' => 'has-tooltip', 1051 + 'meta' => array( 1052 + 'tip' => $tooltip, 1053 + 'align' => 'E', 1054 + 'size' => 600, 1055 + ), 1056 + ), 1057 + id(new PhutilUTF8StringTruncator()) 1058 + ->setMaximumGlyphs(9) 1059 + ->setTerminator('') 1060 + ->truncateString($line['commit'])); 1061 + 1062 + $revision_id = null; 1063 + if (idx($commits, $commit)) { 1064 + $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); 1065 + } 1066 + 1067 + if ($revision_id) { 1068 + $revision = idx($revisions, $revision_id); 1069 + if ($revision) { 1070 + $tooltip = $this->renderRevisionTooltip($revision, $handles); 1071 + $revision_link = javelin_tag( 1072 + 'a', 1073 + array( 1074 + 'href' => '/D'.$revision->getID(), 1075 + 'sigil' => 'has-tooltip', 1076 + 'meta' => array( 1077 + 'tip' => $tooltip, 1078 + 'align' => 'E', 1079 + 'size' => 600, 1080 + ), 1081 + ), 1082 + 'D'.$revision->getID()); 1083 + } 1084 + } 1085 + 1086 + $uri = $line_href->alter('before', $commit); 1087 + $before_link = javelin_tag( 1088 + 'a', 1089 + array( 1090 + 'href' => $uri->setQueryParam('view', 'blame'), 1091 + 'sigil' => 'has-tooltip', 1092 + 'meta' => array( 1093 + 'tip' => pht('Skip Past This Commit'), 1094 + 'align' => 'E', 1095 + 'size' => 300, 1096 + ), 1097 + ), 1098 + "\xC2\xAB"); 1099 + } 1100 + 1101 + $blame[] = phutil_tag( 1102 + 'th', 1103 + array( 1104 + 'class' => 'diffusion-blame-link', 1105 + ), 1106 + $before_link); 1107 + 1108 + $object_links = array(); 1109 + $object_links[] = $commit_link; 1110 + if ($revision_link) { 1111 + $object_links[] = phutil_tag('span', array(), '/'); 1112 + $object_links[] = $revision_link; 1113 + } 1114 + 1115 + $blame[] = phutil_tag( 1116 + 'th', 1117 + array( 1118 + 'class' => 'diffusion-rev-link', 1119 + ), 1120 + $object_links); 1121 + } 1122 + 1123 + $line_link = phutil_tag( 1124 + 'a', 1125 + array( 1126 + 'href' => $line_href, 1127 + 'style' => $style, 1128 + ), 1129 + $line['line']); 1130 + 1131 + $blame[] = javelin_tag( 1132 + 'th', 1133 + array( 1134 + 'class' => 'diffusion-line-link', 1135 + 'sigil' => 'phabricator-source-line', 1136 + 'style' => $style, 1137 + ), 1138 + $line_link); 1139 + 1140 + Javelin::initBehavior('phabricator-line-linker'); 1141 + 1142 + if ($line['target']) { 1143 + Javelin::initBehavior( 1144 + 'diffusion-jump-to', 1145 + array( 1146 + 'target' => 'scroll_target', 1147 + )); 1148 + $anchor_text = phutil_tag( 1149 + 'a', 1150 + array( 1151 + 'id' => 'scroll_target', 1152 + ), 1153 + ''); 1154 + } else { 1155 + $anchor_text = null; 1156 + } 1157 + 1158 + $blame[] = phutil_tag( 1159 + 'td', 1160 + array( 1161 + ), 1162 + array( 1163 + $anchor_text, 1164 + 1165 + // NOTE: See phabricator-oncopy behavior. 1166 + "\xE2\x80\x8B", 1167 + 1168 + // TODO: [HTML] Not ideal. 1169 + phutil_safe_html(str_replace("\t", ' ', $line['data'])), 1170 + )); 1171 + 1172 + if ($this->coverage) { 1173 + require_celerity_resource('differential-changeset-view-css'); 1174 + $cov_index = $line['line'] - 1; 1175 + 1176 + if (isset($this->coverage[$cov_index])) { 1177 + $cov_class = $this->coverage[$cov_index]; 1178 + } else { 1179 + $cov_class = 'N'; 1180 + } 1181 + 1182 + $blame[] = phutil_tag( 1183 + 'td', 1184 + array( 1185 + 'class' => 'cov cov-'.$cov_class, 1186 + ), 1187 + ''); 1188 + } 1189 + 1190 + $rows[] = phutil_tag( 1191 + 'tr', 1192 + array( 1193 + 'class' => ($line['highlighted'] ? 1194 + 'phabricator-source-highlight' : 1195 + null), 1196 + ), 1197 + $blame); 1198 + 1199 + $cur_inlines = $this->renderInlines( 1200 + idx($inlines, $line['line'], array()), 1201 + $show_blame, 1202 + $this->coverage, 1203 + $engine); 1204 + foreach ($cur_inlines as $cur_inline) { 1205 + $rows[] = $cur_inline; 1206 + } 1207 + } 1208 + 1209 + return $rows; 1210 + } 1211 + 1212 + private function renderInlines( 1213 + array $inlines, 1214 + $needs_blame, 1215 + $has_coverage, 1216 + $engine) { 1217 + 1218 + $rows = array(); 1219 + foreach ($inlines as $inline) { 1220 + 1221 + // TODO: This should use modern scaffolding code. 1222 + 1223 + $inline_view = id(new PHUIDiffInlineCommentDetailView()) 1224 + ->setUser($this->getViewer()) 1225 + ->setMarkupEngine($engine) 1226 + ->setInlineComment($inline) 1227 + ->render(); 1228 + 1229 + $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); 1230 + 1231 + $row[] = phutil_tag('td', array(), $inline_view); 1232 + 1233 + if ($has_coverage) { 1234 + $row[] = phutil_tag( 1235 + 'td', 1236 + array( 1237 + 'class' => 'cov cov-I', 1238 + )); 1239 + } 1240 + 1241 + $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); 1242 + } 1243 + 1244 + return $rows; 1245 + } 1246 + 1247 + private function loadFileForData($path, $data) { 1248 + $file = PhabricatorFile::buildFromFileDataOrHash( 1249 + $data, 1250 + array( 1251 + 'name' => basename($path), 1252 + 'ttl' => time() + 60 * 60 * 24, 1253 + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, 1254 + )); 1255 + 1256 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 1257 + $file->attachToObject( 1258 + $this->getDiffusionRequest()->getRepository()->getPHID()); 1259 + unset($unguarded); 1260 + 1261 + return $file; 1262 + } 1263 + 1264 + private function buildRawResponse($path, $data) { 1265 + $file = $this->loadFileForData($path, $data); 1266 + return $file->getRedirectResponse(); 1267 + } 1268 + 1269 + private function buildImageCorpus($file_uri) { 1270 + $properties = new PHUIPropertyListView(); 1271 + 1272 + $properties->addImageContent( 1273 + phutil_tag( 1274 + 'img', 1275 + array( 1276 + 'src' => $file_uri, 1277 + ))); 1278 + 1279 + $file = $this->renderFileButton($file_uri); 1280 + $header = id(new PHUIHeaderView()) 1281 + ->setHeader(pht('Image')) 1282 + ->addActionLink($file); 1283 + 1284 + return id(new PHUIObjectBoxView()) 1285 + ->setHeader($header) 1286 + ->addPropertyList($properties); 1287 + } 1288 + 1289 + private function buildBinaryCorpus($file_uri, $data) { 1290 + 1291 + $size = new PhutilNumber(strlen($data)); 1292 + $text = pht('This is a binary file. It is %s byte(s) in length.', $size); 1293 + $text = id(new PHUIBoxView()) 1294 + ->addPadding(PHUI::PADDING_LARGE) 1295 + ->appendChild($text); 1296 + 1297 + $file = $this->renderFileButton($file_uri); 1298 + $header = id(new PHUIHeaderView()) 1299 + ->setHeader(pht('Details')) 1300 + ->addActionLink($file); 1301 + 1302 + $box = id(new PHUIObjectBoxView()) 1303 + ->setHeader($header) 1304 + ->appendChild($text); 1305 + 1306 + return $box; 1307 + } 1308 + 1309 + private function buildErrorCorpus($message) { 1310 + $text = id(new PHUIBoxView()) 1311 + ->addPadding(PHUI::PADDING_LARGE) 1312 + ->appendChild($message); 1313 + 1314 + $header = id(new PHUIHeaderView()) 1315 + ->setHeader(pht('Details')); 1316 + 1317 + $box = id(new PHUIObjectBoxView()) 1318 + ->setHeader($header) 1319 + ->appendChild($text); 1320 + 1321 + return $box; 1322 + } 1323 + 1324 + private function buildBeforeResponse($before) { 1325 + $request = $this->getRequest(); 1326 + $drequest = $this->getDiffusionRequest(); 1327 + 1328 + // NOTE: We need to get the grandparent so we can capture filename changes 1329 + // in the parent. 1330 + 1331 + $parent = $this->loadParentCommitOf($before); 1332 + $old_filename = null; 1333 + $was_created = false; 1334 + if ($parent) { 1335 + $grandparent = $this->loadParentCommitOf($parent); 1336 + 1337 + if ($grandparent) { 1338 + $rename_query = new DiffusionRenameHistoryQuery(); 1339 + $rename_query->setRequest($drequest); 1340 + $rename_query->setOldCommit($grandparent); 1341 + $rename_query->setViewer($request->getUser()); 1342 + $old_filename = $rename_query->loadOldFilename(); 1343 + $was_created = $rename_query->getWasCreated(); 1344 + } 1345 + } 1346 + 1347 + $follow = null; 1348 + if ($was_created) { 1349 + // If the file was created in history, that means older commits won't 1350 + // have it. Since we know it existed at 'before', it must have been 1351 + // created then; jump there. 1352 + $target_commit = $before; 1353 + $follow = 'created'; 1354 + } else if ($parent) { 1355 + // If we found a parent, jump to it. This is the normal case. 1356 + $target_commit = $parent; 1357 + } else { 1358 + // If there's no parent, this was probably created in the initial commit? 1359 + // And the "was_created" check will fail because we can't identify the 1360 + // grandparent. Keep the user at 'before'. 1361 + $target_commit = $before; 1362 + $follow = 'first'; 1363 + } 1364 + 1365 + $path = $drequest->getPath(); 1366 + $renamed = null; 1367 + if ($old_filename !== null && 1368 + $old_filename !== '/'.$path) { 1369 + $renamed = $path; 1370 + $path = $old_filename; 1371 + } 1372 + 1373 + $line = null; 1374 + // If there's a follow error, drop the line so the user sees the message. 1375 + if (!$follow) { 1376 + $line = $this->getBeforeLineNumber($target_commit); 1377 + } 1378 + 1379 + $before_uri = $drequest->generateURI( 1380 + array( 1381 + 'action' => 'browse', 1382 + 'commit' => $target_commit, 1383 + 'line' => $line, 1384 + 'path' => $path, 1385 + )); 1386 + 1387 + $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); 1388 + $before_uri = $before_uri->alter('before', null); 1389 + $before_uri = $before_uri->alter('renamed', $renamed); 1390 + $before_uri = $before_uri->alter('follow', $follow); 1391 + 1392 + return id(new AphrontRedirectResponse())->setURI($before_uri); 1393 + } 1394 + 1395 + private function getBeforeLineNumber($target_commit) { 1396 + $drequest = $this->getDiffusionRequest(); 1397 + 1398 + $line = $drequest->getLine(); 1399 + if (!$line) { 1400 + return null; 1401 + } 1402 + 1403 + $raw_diff = $this->callConduitWithDiffusionRequest( 1404 + 'diffusion.rawdiffquery', 1405 + array( 1406 + 'commit' => $drequest->getCommit(), 1407 + 'path' => $drequest->getPath(), 1408 + 'againstCommit' => $target_commit, 1409 + )); 1410 + $old_line = 0; 1411 + $new_line = 0; 1412 + 1413 + foreach (explode("\n", $raw_diff) as $text) { 1414 + if ($text[0] == '-' || $text[0] == ' ') { 1415 + $old_line++; 1416 + } 1417 + if ($text[0] == '+' || $text[0] == ' ') { 1418 + $new_line++; 1419 + } 1420 + if ($new_line == $line) { 1421 + return $old_line; 1422 + } 1423 + } 1424 + 1425 + // We didn't find the target line. 1426 + return $line; 1427 + } 1428 + 1429 + private function loadParentCommitOf($commit) { 1430 + $drequest = $this->getDiffusionRequest(); 1431 + $user = $this->getRequest()->getUser(); 1432 + 1433 + $before_req = DiffusionRequest::newFromDictionary( 1434 + array( 1435 + 'user' => $user, 1436 + 'repository' => $drequest->getRepository(), 1437 + 'commit' => $commit, 1438 + )); 1439 + 1440 + $parents = DiffusionQuery::callConduitWithDiffusionRequest( 1441 + $user, 1442 + $before_req, 1443 + 'diffusion.commitparentsquery', 1444 + array( 1445 + 'commit' => $commit, 1446 + )); 1447 + 1448 + return head($parents); 1449 + } 1450 + 1451 + private function renderRevisionTooltip( 1452 + DifferentialRevision $revision, 1453 + array $handles) { 1454 + $viewer = $this->getRequest()->getUser(); 1455 + 1456 + $date = phabricator_date($revision->getDateModified(), $viewer); 1457 + $id = $revision->getID(); 1458 + $title = $revision->getTitle(); 1459 + $header = "D{$id} {$title}"; 1460 + 1461 + $author = $handles[$revision->getAuthorPHID()]->getName(); 1462 + 1463 + return "{$header}\n{$date} \xC2\xB7 {$author}"; 1464 + } 1465 + 1466 + private function renderCommitTooltip( 1467 + PhabricatorRepositoryCommit $commit, 1468 + array $handles, 1469 + $author) { 1470 + 1471 + $viewer = $this->getRequest()->getUser(); 1472 + 1473 + $date = phabricator_date($commit->getEpoch(), $viewer); 1474 + $summary = trim($commit->getSummary()); 1475 + 1476 + if ($commit->getAuthorPHID()) { 1477 + $author = $handles[$commit->getAuthorPHID()]->getName(); 1478 + } 1479 + 1480 + return "{$summary}\n{$date} \xC2\xB7 {$author}"; 1481 + } 1482 + 9 1483 protected function renderSearchForm($collapsed) { 10 1484 $drequest = $this->getDiffusionRequest(); 11 1485 ··· 204 1678 return $view; 205 1679 } 206 1680 207 - protected function buildOpenRevisions() { 208 - $user = $this->getRequest()->getUser(); 1681 + private function buildOpenRevisions() { 1682 + $viewer = $this->getViewer(); 209 1683 210 1684 $drequest = $this->getDiffusionRequest(); 211 1685 $repository = $drequest->getRepository(); ··· 220 1694 $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); 221 1695 222 1696 $revisions = id(new DifferentialRevisionQuery()) 223 - ->setViewer($user) 1697 + ->setViewer($viewer) 224 1698 ->withPath($repository->getID(), $path_id) 225 1699 ->withStatus(DifferentialRevisionQuery::STATUS_OPEN) 226 1700 ->withUpdatedEpochBetween($recent, null) ··· 243 1717 $view = id(new DifferentialRevisionListView()) 244 1718 ->setHeader($header) 245 1719 ->setRevisions($revisions) 246 - ->setUser($user); 1720 + ->setUser($viewer); 247 1721 248 1722 $phids = $view->getRequiredHandlePHIDs(); 249 1723 $handles = $this->loadViewerHandles($phids);
-106
src/applications/diffusion/controller/DiffusionBrowseDirectoryController.php
··· 1 - <?php 2 - 3 - final class DiffusionBrowseDirectoryController 4 - extends DiffusionBrowseController { 5 - 6 - private $browseQueryResults; 7 - 8 - public function setBrowseQueryResults(DiffusionBrowseResultSet $results) { 9 - $this->browseQueryResults = $results; 10 - return $this; 11 - } 12 - 13 - public function getBrowseQueryResults() { 14 - return $this->browseQueryResults; 15 - } 16 - 17 - protected function processDiffusionRequest(AphrontRequest $request) { 18 - $drequest = $this->diffusionRequest; 19 - 20 - $results = $this->getBrowseQueryResults(); 21 - $reason = $results->getReasonForEmptyResultSet(); 22 - 23 - $content = array(); 24 - $actions = $this->buildActionView($drequest); 25 - $properties = $this->buildPropertyView($drequest, $actions); 26 - 27 - $object_box = id(new PHUIObjectBoxView()) 28 - ->setHeader($this->buildHeaderView($drequest)) 29 - ->addPropertyList($properties); 30 - 31 - $content[] = $object_box; 32 - $content[] = $this->renderSearchForm($collapsed = true); 33 - 34 - if (!$results->isValidResults()) { 35 - $empty_result = new DiffusionEmptyResultView(); 36 - $empty_result->setDiffusionRequest($drequest); 37 - $empty_result->setDiffusionBrowseResultSet($results); 38 - $empty_result->setView($request->getStr('view')); 39 - $content[] = $empty_result; 40 - } else { 41 - $phids = array(); 42 - foreach ($results->getPaths() as $result) { 43 - $data = $result->getLastCommitData(); 44 - if ($data) { 45 - if ($data->getCommitDetail('authorPHID')) { 46 - $phids[$data->getCommitDetail('authorPHID')] = true; 47 - } 48 - } 49 - } 50 - 51 - $phids = array_keys($phids); 52 - $handles = $this->loadViewerHandles($phids); 53 - 54 - $browse_table = new DiffusionBrowseTableView(); 55 - $browse_table->setDiffusionRequest($drequest); 56 - $browse_table->setHandles($handles); 57 - $browse_table->setPaths($results->getPaths()); 58 - $browse_table->setUser($request->getUser()); 59 - 60 - $browse_panel = new PHUIObjectBoxView(); 61 - $browse_panel->setHeaderText($drequest->getPath(), '/'); 62 - $browse_panel->setTable($browse_table); 63 - 64 - $content[] = $browse_panel; 65 - } 66 - 67 - $content[] = $this->buildOpenRevisions(); 68 - 69 - 70 - $readme_path = $results->getReadmePath(); 71 - if ($readme_path) { 72 - $readme_content = $this->callConduitWithDiffusionRequest( 73 - 'diffusion.filecontentquery', 74 - array( 75 - 'path' => $readme_path, 76 - 'commit' => $drequest->getStableCommit(), 77 - )); 78 - if ($readme_content) { 79 - $content[] = id(new DiffusionReadmeView()) 80 - ->setUser($this->getViewer()) 81 - ->setPath($readme_path) 82 - ->setContent($readme_content['corpus']); 83 - } 84 - } 85 - 86 - $crumbs = $this->buildCrumbs( 87 - array( 88 - 'branch' => true, 89 - 'path' => true, 90 - 'view' => 'browse', 91 - )); 92 - 93 - return $this->buildApplicationPage( 94 - array( 95 - $crumbs, 96 - $content, 97 - ), 98 - array( 99 - 'title' => array( 100 - nonempty(basename($drequest->getPath()), '/'), 101 - $drequest->getRepository()->getDisplayName(), 102 - ), 103 - )); 104 - } 105 - 106 - }
-1134
src/applications/diffusion/controller/DiffusionBrowseFileController.php
··· 1 - <?php 2 - 3 - final class DiffusionBrowseFileController extends DiffusionBrowseController { 4 - 5 - private $lintCommit; 6 - private $lintMessages; 7 - private $coverage; 8 - 9 - protected function processDiffusionRequest(AphrontRequest $request) { 10 - $drequest = $this->getDiffusionRequest(); 11 - $viewer = $request->getUser(); 12 - 13 - $before = $request->getStr('before'); 14 - if ($before) { 15 - return $this->buildBeforeResponse($before); 16 - } 17 - 18 - $path = $drequest->getPath(); 19 - 20 - $preferences = $viewer->loadPreferences(); 21 - 22 - $show_blame = $request->getBool( 23 - 'blame', 24 - $preferences->getPreference( 25 - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, 26 - false)); 27 - $show_color = $request->getBool( 28 - 'color', 29 - $preferences->getPreference( 30 - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, 31 - true)); 32 - 33 - $view = $request->getStr('view'); 34 - if ($request->isFormPost() && $view != 'raw' && $viewer->isLoggedIn()) { 35 - $preferences->setPreference( 36 - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_BLAME, 37 - $show_blame); 38 - $preferences->setPreference( 39 - PhabricatorUserPreferences::PREFERENCE_DIFFUSION_COLOR, 40 - $show_color); 41 - $preferences->save(); 42 - 43 - $uri = $request->getRequestURI() 44 - ->alter('blame', null) 45 - ->alter('color', null); 46 - 47 - return id(new AphrontRedirectResponse())->setURI($uri); 48 - } 49 - 50 - // We need the blame information if blame is on and we're building plain 51 - // text, or blame is on and this is an Ajax request. If blame is on and 52 - // this is a colorized request, we don't show blame at first (we ajax it 53 - // in afterward) so we don't need to query for it. 54 - $needs_blame = ($show_blame && !$show_color) || 55 - ($show_blame && $request->isAjax()); 56 - 57 - $params = array( 58 - 'commit' => $drequest->getCommit(), 59 - 'path' => $drequest->getPath(), 60 - 'needsBlame' => $needs_blame, 61 - ); 62 - 63 - $byte_limit = null; 64 - if ($view !== 'raw') { 65 - $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); 66 - $time_limit = 10; 67 - 68 - $params += array( 69 - 'timeout' => $time_limit, 70 - 'byteLimit' => $byte_limit, 71 - ); 72 - } 73 - 74 - $file_content = DiffusionFileContent::newFromConduit( 75 - $this->callConduitWithDiffusionRequest( 76 - 'diffusion.filecontentquery', 77 - $params)); 78 - $data = $file_content->getCorpus(); 79 - 80 - if ($view === 'raw') { 81 - return $this->buildRawResponse($path, $data); 82 - } 83 - 84 - $this->loadLintMessages(); 85 - $this->coverage = $drequest->loadCoverage(); 86 - 87 - if ($byte_limit && (strlen($data) == $byte_limit)) { 88 - $corpus = $this->buildErrorCorpus( 89 - pht( 90 - 'This file is larger than %s byte(s), and too large to display '. 91 - 'in the web UI.', 92 - $byte_limit)); 93 - } else if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) { 94 - $file = $this->loadFileForData($path, $data); 95 - $file_uri = $file->getBestURI(); 96 - 97 - if ($file->isViewableImage()) { 98 - $corpus = $this->buildImageCorpus($file_uri); 99 - } else { 100 - $corpus = $this->buildBinaryCorpus($file_uri, $data); 101 - } 102 - } else { 103 - // Build the content of the file. 104 - $corpus = $this->buildCorpus( 105 - $show_blame, 106 - $show_color, 107 - $file_content, 108 - $needs_blame, 109 - $drequest, 110 - $path, 111 - $data); 112 - } 113 - 114 - if ($request->isAjax()) { 115 - return id(new AphrontAjaxResponse())->setContent($corpus); 116 - } 117 - 118 - require_celerity_resource('diffusion-source-css'); 119 - 120 - // Render the page. 121 - $view = $this->buildActionView($drequest); 122 - $action_list = $this->enrichActionView( 123 - $view, 124 - $drequest, 125 - $show_blame, 126 - $show_color); 127 - 128 - $properties = $this->buildPropertyView($drequest, $action_list); 129 - $object_box = id(new PHUIObjectBoxView()) 130 - ->setHeader($this->buildHeaderView($drequest)) 131 - ->addPropertyList($properties); 132 - 133 - $content = array(); 134 - $content[] = $object_box; 135 - 136 - $follow = $request->getStr('follow'); 137 - if ($follow) { 138 - $notice = new PHUIInfoView(); 139 - $notice->setSeverity(PHUIInfoView::SEVERITY_WARNING); 140 - $notice->setTitle(pht('Unable to Continue')); 141 - switch ($follow) { 142 - case 'first': 143 - $notice->appendChild( 144 - pht( 145 - 'Unable to continue tracing the history of this file because '. 146 - 'this commit is the first commit in the repository.')); 147 - break; 148 - case 'created': 149 - $notice->appendChild( 150 - pht( 151 - 'Unable to continue tracing the history of this file because '. 152 - 'this commit created the file.')); 153 - break; 154 - } 155 - $content[] = $notice; 156 - } 157 - 158 - $renamed = $request->getStr('renamed'); 159 - if ($renamed) { 160 - $notice = new PHUIInfoView(); 161 - $notice->setSeverity(PHUIInfoView::SEVERITY_NOTICE); 162 - $notice->setTitle(pht('File Renamed')); 163 - $notice->appendChild( 164 - pht( 165 - "File history passes through a rename from '%s' to '%s'.", 166 - $drequest->getPath(), $renamed)); 167 - $content[] = $notice; 168 - } 169 - 170 - $content[] = $corpus; 171 - $content[] = $this->buildOpenRevisions(); 172 - 173 - $crumbs = $this->buildCrumbs( 174 - array( 175 - 'branch' => true, 176 - 'path' => true, 177 - 'view' => 'browse', 178 - )); 179 - 180 - $basename = basename($this->getDiffusionRequest()->getPath()); 181 - 182 - return $this->buildApplicationPage( 183 - array( 184 - $crumbs, 185 - $content, 186 - ), 187 - array( 188 - 'title' => $basename, 189 - )); 190 - } 191 - 192 - private function loadLintMessages() { 193 - $drequest = $this->getDiffusionRequest(); 194 - $branch = $drequest->loadBranch(); 195 - 196 - if (!$branch || !$branch->getLintCommit()) { 197 - return; 198 - } 199 - 200 - $this->lintCommit = $branch->getLintCommit(); 201 - 202 - $conn = id(new PhabricatorRepository())->establishConnection('r'); 203 - 204 - $where = ''; 205 - if ($drequest->getLint()) { 206 - $where = qsprintf( 207 - $conn, 208 - 'AND code = %s', 209 - $drequest->getLint()); 210 - } 211 - 212 - $this->lintMessages = queryfx_all( 213 - $conn, 214 - 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', 215 - PhabricatorRepository::TABLE_LINTMESSAGE, 216 - $branch->getID(), 217 - $where, 218 - '/'.$drequest->getPath()); 219 - } 220 - 221 - private function buildCorpus( 222 - $show_blame, 223 - $show_color, 224 - DiffusionFileContent $file_content, 225 - $needs_blame, 226 - DiffusionRequest $drequest, 227 - $path, 228 - $data) { 229 - 230 - if (!$show_color) { 231 - $style = 232 - 'border: none; width: 100%; height: 80em; font-family: monospace'; 233 - if (!$show_blame) { 234 - $corpus = phutil_tag( 235 - 'textarea', 236 - array( 237 - 'style' => $style, 238 - ), 239 - $file_content->getCorpus()); 240 - } else { 241 - $text_list = $file_content->getTextList(); 242 - $rev_list = $file_content->getRevList(); 243 - $blame_dict = $file_content->getBlameDict(); 244 - 245 - $rows = array(); 246 - foreach ($text_list as $k => $line) { 247 - $rev = $rev_list[$k]; 248 - $author = $blame_dict[$rev]['author']; 249 - $rows[] = 250 - sprintf('%-10s %-20s %s', substr($rev, 0, 7), $author, $line); 251 - } 252 - 253 - $corpus = phutil_tag( 254 - 'textarea', 255 - array( 256 - 'style' => $style, 257 - ), 258 - implode("\n", $rows)); 259 - } 260 - } else { 261 - require_celerity_resource('syntax-highlighting-css'); 262 - $text_list = $file_content->getTextList(); 263 - $rev_list = $file_content->getRevList(); 264 - $blame_dict = $file_content->getBlameDict(); 265 - 266 - $text_list = implode("\n", $text_list); 267 - $text_list = PhabricatorSyntaxHighlighter::highlightWithFilename( 268 - $path, 269 - $text_list); 270 - $text_list = explode("\n", $text_list); 271 - 272 - $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, 273 - $needs_blame, $drequest, $show_blame, $show_color); 274 - 275 - $corpus_table = javelin_tag( 276 - 'table', 277 - array( 278 - 'class' => 'diffusion-source remarkup-code PhabricatorMonospaced', 279 - 'sigil' => 'phabricator-source', 280 - ), 281 - $rows); 282 - 283 - if ($this->getRequest()->isAjax()) { 284 - return $corpus_table; 285 - } 286 - 287 - $id = celerity_generate_unique_node_id(); 288 - 289 - $repo = $drequest->getRepository(); 290 - $symbol_repos = nonempty($repo->getSymbolSources(), array()); 291 - $symbol_repos[] = $repo->getPHID(); 292 - 293 - $lang = last(explode('.', $drequest->getPath())); 294 - $repo_languages = $repo->getSymbolLanguages(); 295 - $repo_languages = nonempty($repo_languages, array()); 296 - $repo_languages = array_fill_keys($repo_languages, true); 297 - 298 - $needs_symbols = true; 299 - if ($repo_languages && $symbol_repos) { 300 - $have_symbols = id(new DiffusionSymbolQuery()) 301 - ->existsSymbolsInRepository($repo->getPHID()); 302 - if (!$have_symbols) { 303 - $needs_symbols = false; 304 - } 305 - } 306 - 307 - if ($needs_symbols && $repo_languages) { 308 - $needs_symbols = isset($repo_languages[$lang]); 309 - } 310 - 311 - if ($needs_symbols) { 312 - Javelin::initBehavior( 313 - 'repository-crossreference', 314 - array( 315 - 'container' => $id, 316 - 'lang' => $lang, 317 - 'repositories' => $symbol_repos, 318 - )); 319 - } 320 - 321 - $corpus = phutil_tag( 322 - 'div', 323 - array( 324 - 'id' => $id, 325 - ), 326 - $corpus_table); 327 - 328 - Javelin::initBehavior('load-blame', array('id' => $id)); 329 - } 330 - 331 - $edit = $this->renderEditButton(); 332 - $file = $this->renderFileButton(); 333 - $header = id(new PHUIHeaderView()) 334 - ->setHeader(pht('File Contents')) 335 - ->addActionLink($edit) 336 - ->addActionLink($file); 337 - 338 - $corpus = id(new PHUIObjectBoxView()) 339 - ->setHeader($header) 340 - ->appendChild($corpus) 341 - ->setCollapsed(true); 342 - 343 - return $corpus; 344 - } 345 - 346 - private function enrichActionView( 347 - PhabricatorActionListView $view, 348 - DiffusionRequest $drequest, 349 - $show_blame, 350 - $show_color) { 351 - 352 - $viewer = $this->getRequest()->getUser(); 353 - $base_uri = $this->getRequest()->getRequestURI(); 354 - 355 - $view->addAction( 356 - id(new PhabricatorActionView()) 357 - ->setName(pht('Show Last Change')) 358 - ->setHref( 359 - $drequest->generateURI( 360 - array( 361 - 'action' => 'change', 362 - ))) 363 - ->setIcon('fa-backward')); 364 - 365 - if ($show_blame) { 366 - $blame_text = pht('Disable Blame'); 367 - $blame_icon = 'fa-exclamation-circle lightgreytext'; 368 - $blame_value = 0; 369 - } else { 370 - $blame_text = pht('Enable Blame'); 371 - $blame_icon = 'fa-exclamation-circle'; 372 - $blame_value = 1; 373 - } 374 - 375 - $view->addAction( 376 - id(new PhabricatorActionView()) 377 - ->setName($blame_text) 378 - ->setHref($base_uri->alter('blame', $blame_value)) 379 - ->setIcon($blame_icon) 380 - ->setUser($viewer) 381 - ->setRenderAsForm($viewer->isLoggedIn())); 382 - 383 - if ($show_color) { 384 - $highlight_text = pht('Disable Highlighting'); 385 - $highlight_icon = 'fa-star-o grey'; 386 - $highlight_value = 0; 387 - } else { 388 - $highlight_text = pht('Enable Highlighting'); 389 - $highlight_icon = 'fa-star'; 390 - $highlight_value = 1; 391 - } 392 - 393 - $view->addAction( 394 - id(new PhabricatorActionView()) 395 - ->setName($highlight_text) 396 - ->setHref($base_uri->alter('color', $highlight_value)) 397 - ->setIcon($highlight_icon) 398 - ->setUser($viewer) 399 - ->setRenderAsForm($viewer->isLoggedIn())); 400 - 401 - $href = null; 402 - if ($this->getRequest()->getStr('lint') !== null) { 403 - $lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages)); 404 - $href = $base_uri->alter('lint', null); 405 - 406 - } else if ($this->lintCommit === null) { 407 - $lint_text = pht('Lint not Available'); 408 - } else { 409 - $lint_text = pht( 410 - 'Show %d Lint Message(s)', 411 - count($this->lintMessages)); 412 - $href = $this->getDiffusionRequest()->generateURI(array( 413 - 'action' => 'browse', 414 - 'commit' => $this->lintCommit, 415 - ))->alter('lint', ''); 416 - } 417 - 418 - $view->addAction( 419 - id(new PhabricatorActionView()) 420 - ->setName($lint_text) 421 - ->setHref($href) 422 - ->setIcon('fa-exclamation-triangle') 423 - ->setDisabled(!$href)); 424 - 425 - return $view; 426 - } 427 - 428 - private function renderEditButton() { 429 - $request = $this->getRequest(); 430 - $user = $request->getUser(); 431 - 432 - $drequest = $this->getDiffusionRequest(); 433 - 434 - $repository = $drequest->getRepository(); 435 - $path = $drequest->getPath(); 436 - $line = nonempty((int)$drequest->getLine(), 1); 437 - 438 - $editor_link = $user->loadEditorLink($path, $line, $repository); 439 - $template = $user->loadEditorLink($path, '%l', $repository); 440 - 441 - $icon_edit = id(new PHUIIconView()) 442 - ->setIconFont('fa-pencil'); 443 - $button = id(new PHUIButtonView()) 444 - ->setTag('a') 445 - ->setText(pht('Open in Editor')) 446 - ->setHref($editor_link) 447 - ->setIcon($icon_edit) 448 - ->setID('editor_link') 449 - ->setMetadata(array('link_template' => $template)) 450 - ->setDisabled(!$editor_link); 451 - 452 - return $button; 453 - } 454 - 455 - private function renderFileButton($file_uri = null) { 456 - 457 - $base_uri = $this->getRequest()->getRequestURI(); 458 - 459 - if ($file_uri) { 460 - $text = pht('Download Raw File'); 461 - $href = $file_uri; 462 - $icon = 'fa-download'; 463 - } else { 464 - $text = pht('View Raw File'); 465 - $href = $base_uri->alter('view', 'raw'); 466 - $icon = 'fa-file-text'; 467 - } 468 - 469 - $iconview = id(new PHUIIconView()) 470 - ->setIconFont($icon); 471 - $button = id(new PHUIButtonView()) 472 - ->setTag('a') 473 - ->setText($text) 474 - ->setHref($href) 475 - ->setIcon($iconview); 476 - 477 - return $button; 478 - } 479 - 480 - 481 - private function buildDisplayRows( 482 - array $text_list, 483 - array $rev_list, 484 - array $blame_dict, 485 - $needs_blame, 486 - DiffusionRequest $drequest, 487 - $show_blame, 488 - $show_color) { 489 - 490 - $handles = array(); 491 - if ($blame_dict) { 492 - $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); 493 - $epoch_min = min($epoch_list); 494 - $epoch_max = max($epoch_list); 495 - $epoch_range = ($epoch_max - $epoch_min) + 1; 496 - 497 - $author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID'); 498 - $handles = $this->loadViewerHandles($author_phids); 499 - } 500 - 501 - $line_arr = array(); 502 - $line_str = $drequest->getLine(); 503 - $ranges = explode(',', $line_str); 504 - foreach ($ranges as $range) { 505 - if (strpos($range, '-') !== false) { 506 - list($min, $max) = explode('-', $range, 2); 507 - $line_arr[] = array( 508 - 'min' => min($min, $max), 509 - 'max' => max($min, $max), 510 - ); 511 - } else if (strlen($range)) { 512 - $line_arr[] = array( 513 - 'min' => $range, 514 - 'max' => $range, 515 - ); 516 - } 517 - } 518 - 519 - $display = array(); 520 - 521 - $line_number = 1; 522 - $last_rev = null; 523 - $color = null; 524 - foreach ($text_list as $k => $line) { 525 - $display_line = array( 526 - 'epoch' => null, 527 - 'commit' => null, 528 - 'author' => null, 529 - 'target' => null, 530 - 'highlighted' => null, 531 - 'line' => $line_number, 532 - 'data' => $line, 533 - ); 534 - 535 - if ($show_blame) { 536 - // If the line's rev is same as the line above, show empty content 537 - // with same color; otherwise generate blame info. The newer a change 538 - // is, the more saturated the color. 539 - 540 - $rev = idx($rev_list, $k, $last_rev); 541 - 542 - if ($last_rev == $rev) { 543 - $display_line['color'] = $color; 544 - } else { 545 - $blame = $blame_dict[$rev]; 546 - 547 - if (!isset($blame['epoch'])) { 548 - $color = '#ffd'; // Render as warning. 549 - } else { 550 - $color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range; 551 - $color_value = 0xE6 * (1.0 - $color_ratio); 552 - $color = sprintf( 553 - '#%02x%02x%02x', 554 - $color_value, 555 - 0xF6, 556 - $color_value); 557 - } 558 - 559 - $display_line['epoch'] = idx($blame, 'epoch'); 560 - $display_line['color'] = $color; 561 - $display_line['commit'] = $rev; 562 - 563 - $author_phid = idx($blame, 'authorPHID'); 564 - if ($author_phid && $handles[$author_phid]) { 565 - $author_link = $handles[$author_phid]->renderLink(); 566 - } else { 567 - $author_link = $blame['author']; 568 - } 569 - $display_line['author'] = $author_link; 570 - 571 - $last_rev = $rev; 572 - } 573 - } 574 - 575 - if ($line_arr) { 576 - if ($line_number == $line_arr[0]['min']) { 577 - $display_line['target'] = true; 578 - } 579 - foreach ($line_arr as $range) { 580 - if ($line_number >= $range['min'] && 581 - $line_number <= $range['max']) { 582 - $display_line['highlighted'] = true; 583 - } 584 - } 585 - } 586 - 587 - $display[] = $display_line; 588 - ++$line_number; 589 - } 590 - 591 - $request = $this->getRequest(); 592 - $viewer = $request->getUser(); 593 - 594 - $commits = array_filter(ipull($display, 'commit')); 595 - if ($commits) { 596 - $commits = id(new DiffusionCommitQuery()) 597 - ->setViewer($viewer) 598 - ->withRepository($drequest->getRepository()) 599 - ->withIdentifiers($commits) 600 - ->execute(); 601 - $commits = mpull($commits, null, 'getCommitIdentifier'); 602 - } 603 - 604 - $revision_ids = id(new DifferentialRevision()) 605 - ->loadIDsByCommitPHIDs(mpull($commits, 'getPHID')); 606 - $revisions = array(); 607 - if ($revision_ids) { 608 - $revisions = id(new DifferentialRevisionQuery()) 609 - ->setViewer($viewer) 610 - ->withIDs($revision_ids) 611 - ->execute(); 612 - } 613 - 614 - $phids = array(); 615 - foreach ($commits as $commit) { 616 - if ($commit->getAuthorPHID()) { 617 - $phids[] = $commit->getAuthorPHID(); 618 - } 619 - } 620 - foreach ($revisions as $revision) { 621 - if ($revision->getAuthorPHID()) { 622 - $phids[] = $revision->getAuthorPHID(); 623 - } 624 - } 625 - $handles = $this->loadViewerHandles($phids); 626 - 627 - Javelin::initBehavior('phabricator-oncopy', array()); 628 - 629 - $engine = null; 630 - $inlines = array(); 631 - if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) { 632 - $engine = new PhabricatorMarkupEngine(); 633 - $engine->setViewer($viewer); 634 - 635 - foreach ($this->lintMessages as $message) { 636 - $inline = id(new PhabricatorAuditInlineComment()) 637 - ->setSyntheticAuthor( 638 - ArcanistLintSeverity::getStringForSeverity($message['severity']). 639 - ' '.$message['code'].' ('.$message['name'].')') 640 - ->setLineNumber($message['line']) 641 - ->setContent($message['description']); 642 - $inlines[$message['line']][] = $inline; 643 - 644 - $engine->addObject( 645 - $inline, 646 - PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); 647 - } 648 - 649 - $engine->process(); 650 - require_celerity_resource('differential-changeset-view-css'); 651 - } 652 - 653 - $rows = $this->renderInlines( 654 - idx($inlines, 0, array()), 655 - $show_blame, 656 - (bool)$this->coverage, 657 - $engine); 658 - 659 - foreach ($display as $line) { 660 - 661 - $line_href = $drequest->generateURI( 662 - array( 663 - 'action' => 'browse', 664 - 'line' => $line['line'], 665 - 'stable' => true, 666 - )); 667 - 668 - $blame = array(); 669 - $style = null; 670 - if (array_key_exists('color', $line)) { 671 - if ($line['color']) { 672 - $style = 'background: '.$line['color'].';'; 673 - } 674 - 675 - $before_link = null; 676 - $commit_link = null; 677 - $revision_link = null; 678 - if (idx($line, 'commit')) { 679 - $commit = $line['commit']; 680 - 681 - if (idx($commits, $commit)) { 682 - $tooltip = $this->renderCommitTooltip( 683 - $commits[$commit], 684 - $handles, 685 - $line['author']); 686 - } else { 687 - $tooltip = null; 688 - } 689 - 690 - Javelin::initBehavior('phabricator-tooltips', array()); 691 - require_celerity_resource('aphront-tooltip-css'); 692 - 693 - $commit_link = javelin_tag( 694 - 'a', 695 - array( 696 - 'href' => $drequest->generateURI( 697 - array( 698 - 'action' => 'commit', 699 - 'commit' => $line['commit'], 700 - )), 701 - 'sigil' => 'has-tooltip', 702 - 'meta' => array( 703 - 'tip' => $tooltip, 704 - 'align' => 'E', 705 - 'size' => 600, 706 - ), 707 - ), 708 - id(new PhutilUTF8StringTruncator()) 709 - ->setMaximumGlyphs(9) 710 - ->setTerminator('') 711 - ->truncateString($line['commit'])); 712 - 713 - $revision_id = null; 714 - if (idx($commits, $commit)) { 715 - $revision_id = idx($revision_ids, $commits[$commit]->getPHID()); 716 - } 717 - 718 - if ($revision_id) { 719 - $revision = idx($revisions, $revision_id); 720 - if ($revision) { 721 - $tooltip = $this->renderRevisionTooltip($revision, $handles); 722 - $revision_link = javelin_tag( 723 - 'a', 724 - array( 725 - 'href' => '/D'.$revision->getID(), 726 - 'sigil' => 'has-tooltip', 727 - 'meta' => array( 728 - 'tip' => $tooltip, 729 - 'align' => 'E', 730 - 'size' => 600, 731 - ), 732 - ), 733 - 'D'.$revision->getID()); 734 - } 735 - } 736 - 737 - $uri = $line_href->alter('before', $commit); 738 - $before_link = javelin_tag( 739 - 'a', 740 - array( 741 - 'href' => $uri->setQueryParam('view', 'blame'), 742 - 'sigil' => 'has-tooltip', 743 - 'meta' => array( 744 - 'tip' => pht('Skip Past This Commit'), 745 - 'align' => 'E', 746 - 'size' => 300, 747 - ), 748 - ), 749 - "\xC2\xAB"); 750 - } 751 - 752 - $blame[] = phutil_tag( 753 - 'th', 754 - array( 755 - 'class' => 'diffusion-blame-link', 756 - ), 757 - $before_link); 758 - 759 - $object_links = array(); 760 - $object_links[] = $commit_link; 761 - if ($revision_link) { 762 - $object_links[] = phutil_tag('span', array(), '/'); 763 - $object_links[] = $revision_link; 764 - } 765 - 766 - $blame[] = phutil_tag( 767 - 'th', 768 - array( 769 - 'class' => 'diffusion-rev-link', 770 - ), 771 - $object_links); 772 - } 773 - 774 - $line_link = phutil_tag( 775 - 'a', 776 - array( 777 - 'href' => $line_href, 778 - 'style' => $style, 779 - ), 780 - $line['line']); 781 - 782 - $blame[] = javelin_tag( 783 - 'th', 784 - array( 785 - 'class' => 'diffusion-line-link', 786 - 'sigil' => 'phabricator-source-line', 787 - 'style' => $style, 788 - ), 789 - $line_link); 790 - 791 - Javelin::initBehavior('phabricator-line-linker'); 792 - 793 - if ($line['target']) { 794 - Javelin::initBehavior( 795 - 'diffusion-jump-to', 796 - array( 797 - 'target' => 'scroll_target', 798 - )); 799 - $anchor_text = phutil_tag( 800 - 'a', 801 - array( 802 - 'id' => 'scroll_target', 803 - ), 804 - ''); 805 - } else { 806 - $anchor_text = null; 807 - } 808 - 809 - $blame[] = phutil_tag( 810 - 'td', 811 - array( 812 - ), 813 - array( 814 - $anchor_text, 815 - 816 - // NOTE: See phabricator-oncopy behavior. 817 - "\xE2\x80\x8B", 818 - 819 - // TODO: [HTML] Not ideal. 820 - phutil_safe_html(str_replace("\t", ' ', $line['data'])), 821 - )); 822 - 823 - if ($this->coverage) { 824 - require_celerity_resource('differential-changeset-view-css'); 825 - $cov_index = $line['line'] - 1; 826 - 827 - if (isset($this->coverage[$cov_index])) { 828 - $cov_class = $this->coverage[$cov_index]; 829 - } else { 830 - $cov_class = 'N'; 831 - } 832 - 833 - $blame[] = phutil_tag( 834 - 'td', 835 - array( 836 - 'class' => 'cov cov-'.$cov_class, 837 - ), 838 - ''); 839 - } 840 - 841 - $rows[] = phutil_tag( 842 - 'tr', 843 - array( 844 - 'class' => ($line['highlighted'] ? 845 - 'phabricator-source-highlight' : 846 - null), 847 - ), 848 - $blame); 849 - 850 - $cur_inlines = $this->renderInlines( 851 - idx($inlines, $line['line'], array()), 852 - $show_blame, 853 - $this->coverage, 854 - $engine); 855 - foreach ($cur_inlines as $cur_inline) { 856 - $rows[] = $cur_inline; 857 - } 858 - } 859 - 860 - return $rows; 861 - } 862 - 863 - private function renderInlines( 864 - array $inlines, 865 - $needs_blame, 866 - $has_coverage, 867 - $engine) { 868 - 869 - $rows = array(); 870 - foreach ($inlines as $inline) { 871 - 872 - // TODO: This should use modern scaffolding code. 873 - 874 - $inline_view = id(new PHUIDiffInlineCommentDetailView()) 875 - ->setUser($this->getViewer()) 876 - ->setMarkupEngine($engine) 877 - ->setInlineComment($inline) 878 - ->render(); 879 - 880 - $row = array_fill(0, ($needs_blame ? 3 : 1), phutil_tag('th')); 881 - 882 - $row[] = phutil_tag('td', array(), $inline_view); 883 - 884 - if ($has_coverage) { 885 - $row[] = phutil_tag( 886 - 'td', 887 - array( 888 - 'class' => 'cov cov-I', 889 - )); 890 - } 891 - 892 - $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); 893 - } 894 - 895 - return $rows; 896 - } 897 - 898 - private function loadFileForData($path, $data) { 899 - $file = PhabricatorFile::buildFromFileDataOrHash( 900 - $data, 901 - array( 902 - 'name' => basename($path), 903 - 'ttl' => time() + 60 * 60 * 24, 904 - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, 905 - )); 906 - 907 - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 908 - $file->attachToObject( 909 - $this->getDiffusionRequest()->getRepository()->getPHID()); 910 - unset($unguarded); 911 - 912 - return $file; 913 - } 914 - 915 - private function buildRawResponse($path, $data) { 916 - $file = $this->loadFileForData($path, $data); 917 - return $file->getRedirectResponse(); 918 - } 919 - 920 - private function buildImageCorpus($file_uri) { 921 - $properties = new PHUIPropertyListView(); 922 - 923 - $properties->addImageContent( 924 - phutil_tag( 925 - 'img', 926 - array( 927 - 'src' => $file_uri, 928 - ))); 929 - 930 - $file = $this->renderFileButton($file_uri); 931 - $header = id(new PHUIHeaderView()) 932 - ->setHeader(pht('Image')) 933 - ->addActionLink($file); 934 - 935 - return id(new PHUIObjectBoxView()) 936 - ->setHeader($header) 937 - ->addPropertyList($properties); 938 - } 939 - 940 - private function buildBinaryCorpus($file_uri, $data) { 941 - 942 - $size = new PhutilNumber(strlen($data)); 943 - $text = pht('This is a binary file. It is %s byte(s) in length.', $size); 944 - $text = id(new PHUIBoxView()) 945 - ->addPadding(PHUI::PADDING_LARGE) 946 - ->appendChild($text); 947 - 948 - $file = $this->renderFileButton($file_uri); 949 - $header = id(new PHUIHeaderView()) 950 - ->setHeader(pht('Details')) 951 - ->addActionLink($file); 952 - 953 - $box = id(new PHUIObjectBoxView()) 954 - ->setHeader($header) 955 - ->appendChild($text); 956 - 957 - return $box; 958 - } 959 - 960 - private function buildErrorCorpus($message) { 961 - $text = id(new PHUIBoxView()) 962 - ->addPadding(PHUI::PADDING_LARGE) 963 - ->appendChild($message); 964 - 965 - $header = id(new PHUIHeaderView()) 966 - ->setHeader(pht('Details')); 967 - 968 - $box = id(new PHUIObjectBoxView()) 969 - ->setHeader($header) 970 - ->appendChild($text); 971 - 972 - return $box; 973 - } 974 - 975 - private function buildBeforeResponse($before) { 976 - $request = $this->getRequest(); 977 - $drequest = $this->getDiffusionRequest(); 978 - 979 - // NOTE: We need to get the grandparent so we can capture filename changes 980 - // in the parent. 981 - 982 - $parent = $this->loadParentCommitOf($before); 983 - $old_filename = null; 984 - $was_created = false; 985 - if ($parent) { 986 - $grandparent = $this->loadParentCommitOf($parent); 987 - 988 - if ($grandparent) { 989 - $rename_query = new DiffusionRenameHistoryQuery(); 990 - $rename_query->setRequest($drequest); 991 - $rename_query->setOldCommit($grandparent); 992 - $rename_query->setViewer($request->getUser()); 993 - $old_filename = $rename_query->loadOldFilename(); 994 - $was_created = $rename_query->getWasCreated(); 995 - } 996 - } 997 - 998 - $follow = null; 999 - if ($was_created) { 1000 - // If the file was created in history, that means older commits won't 1001 - // have it. Since we know it existed at 'before', it must have been 1002 - // created then; jump there. 1003 - $target_commit = $before; 1004 - $follow = 'created'; 1005 - } else if ($parent) { 1006 - // If we found a parent, jump to it. This is the normal case. 1007 - $target_commit = $parent; 1008 - } else { 1009 - // If there's no parent, this was probably created in the initial commit? 1010 - // And the "was_created" check will fail because we can't identify the 1011 - // grandparent. Keep the user at 'before'. 1012 - $target_commit = $before; 1013 - $follow = 'first'; 1014 - } 1015 - 1016 - $path = $drequest->getPath(); 1017 - $renamed = null; 1018 - if ($old_filename !== null && 1019 - $old_filename !== '/'.$path) { 1020 - $renamed = $path; 1021 - $path = $old_filename; 1022 - } 1023 - 1024 - $line = null; 1025 - // If there's a follow error, drop the line so the user sees the message. 1026 - if (!$follow) { 1027 - $line = $this->getBeforeLineNumber($target_commit); 1028 - } 1029 - 1030 - $before_uri = $drequest->generateURI( 1031 - array( 1032 - 'action' => 'browse', 1033 - 'commit' => $target_commit, 1034 - 'line' => $line, 1035 - 'path' => $path, 1036 - )); 1037 - 1038 - $before_uri->setQueryParams($request->getRequestURI()->getQueryParams()); 1039 - $before_uri = $before_uri->alter('before', null); 1040 - $before_uri = $before_uri->alter('renamed', $renamed); 1041 - $before_uri = $before_uri->alter('follow', $follow); 1042 - 1043 - return id(new AphrontRedirectResponse())->setURI($before_uri); 1044 - } 1045 - 1046 - private function getBeforeLineNumber($target_commit) { 1047 - $drequest = $this->getDiffusionRequest(); 1048 - 1049 - $line = $drequest->getLine(); 1050 - if (!$line) { 1051 - return null; 1052 - } 1053 - 1054 - $raw_diff = $this->callConduitWithDiffusionRequest( 1055 - 'diffusion.rawdiffquery', 1056 - array( 1057 - 'commit' => $drequest->getCommit(), 1058 - 'path' => $drequest->getPath(), 1059 - 'againstCommit' => $target_commit, 1060 - )); 1061 - $old_line = 0; 1062 - $new_line = 0; 1063 - 1064 - foreach (explode("\n", $raw_diff) as $text) { 1065 - if ($text[0] == '-' || $text[0] == ' ') { 1066 - $old_line++; 1067 - } 1068 - if ($text[0] == '+' || $text[0] == ' ') { 1069 - $new_line++; 1070 - } 1071 - if ($new_line == $line) { 1072 - return $old_line; 1073 - } 1074 - } 1075 - 1076 - // We didn't find the target line. 1077 - return $line; 1078 - } 1079 - 1080 - private function loadParentCommitOf($commit) { 1081 - $drequest = $this->getDiffusionRequest(); 1082 - $user = $this->getRequest()->getUser(); 1083 - 1084 - $before_req = DiffusionRequest::newFromDictionary( 1085 - array( 1086 - 'user' => $user, 1087 - 'repository' => $drequest->getRepository(), 1088 - 'commit' => $commit, 1089 - )); 1090 - 1091 - $parents = DiffusionQuery::callConduitWithDiffusionRequest( 1092 - $user, 1093 - $before_req, 1094 - 'diffusion.commitparentsquery', 1095 - array( 1096 - 'commit' => $commit, 1097 - )); 1098 - 1099 - return head($parents); 1100 - } 1101 - 1102 - private function renderRevisionTooltip( 1103 - DifferentialRevision $revision, 1104 - array $handles) { 1105 - $viewer = $this->getRequest()->getUser(); 1106 - 1107 - $date = phabricator_date($revision->getDateModified(), $viewer); 1108 - $id = $revision->getID(); 1109 - $title = $revision->getTitle(); 1110 - $header = "D{$id} {$title}"; 1111 - 1112 - $author = $handles[$revision->getAuthorPHID()]->getName(); 1113 - 1114 - return "{$header}\n{$date} \xC2\xB7 {$author}"; 1115 - } 1116 - 1117 - private function renderCommitTooltip( 1118 - PhabricatorRepositoryCommit $commit, 1119 - array $handles, 1120 - $author) { 1121 - 1122 - $viewer = $this->getRequest()->getUser(); 1123 - 1124 - $date = phabricator_date($commit->getEpoch(), $viewer); 1125 - $summary = trim($commit->getSummary()); 1126 - 1127 - if ($commit->getAuthorPHID()) { 1128 - $author = $handles[$commit->getAuthorPHID()]->getName(); 1129 - } 1130 - 1131 - return "{$summary}\n{$date} \xC2\xB7 {$author}"; 1132 - } 1133 - 1134 - }
-39
src/applications/diffusion/controller/DiffusionBrowseMainController.php
··· 1 - <?php 2 - 3 - final class DiffusionBrowseMainController extends DiffusionBrowseController { 4 - 5 - protected function processDiffusionRequest(AphrontRequest $request) { 6 - $drequest = $this->diffusionRequest; 7 - 8 - // Figure out if we're browsing a directory, a file, or a search result 9 - // list. Then delegate to the appropriate controller. 10 - 11 - $grep = $request->getStr('grep'); 12 - $find = $request->getStr('find'); 13 - if (strlen($grep) || strlen($find)) { 14 - $controller = new DiffusionBrowseSearchController(); 15 - } else { 16 - $results = DiffusionBrowseResultSet::newFromConduit( 17 - $this->callConduitWithDiffusionRequest( 18 - 'diffusion.browsequery', 19 - array( 20 - 'path' => $drequest->getPath(), 21 - 'commit' => $drequest->getStableCommit(), 22 - ))); 23 - $reason = $results->getReasonForEmptyResultSet(); 24 - $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); 25 - 26 - if ($is_file) { 27 - $controller = new DiffusionBrowseFileController($request); 28 - } else { 29 - $controller = new DiffusionBrowseDirectoryController($request); 30 - $controller->setBrowseQueryResults($results); 31 - } 32 - } 33 - 34 - $controller->setDiffusionRequest($drequest); 35 - $controller->setCurrentApplication($this->getCurrentApplication()); 36 - return $this->delegateToController($controller); 37 - } 38 - 39 - }
-231
src/applications/diffusion/controller/DiffusionBrowseSearchController.php
··· 1 - <?php 2 - 3 - final class DiffusionBrowseSearchController extends DiffusionBrowseController { 4 - 5 - protected function processDiffusionRequest(AphrontRequest $request) { 6 - $drequest = $this->diffusionRequest; 7 - 8 - $actions = $this->buildActionView($drequest); 9 - $properties = $this->buildPropertyView($drequest, $actions); 10 - 11 - $object_box = id(new PHUIObjectBoxView()) 12 - ->setHeader($this->buildHeaderView($drequest)) 13 - ->addPropertyList($properties); 14 - 15 - $content = array(); 16 - 17 - $content[] = $object_box; 18 - $content[] = $this->renderSearchForm($collapsed = false); 19 - $content[] = $this->renderSearchResults(); 20 - 21 - $crumbs = $this->buildCrumbs( 22 - array( 23 - 'branch' => true, 24 - 'path' => true, 25 - 'view' => 'browse', 26 - )); 27 - 28 - return $this->buildApplicationPage( 29 - array( 30 - $crumbs, 31 - $content, 32 - ), 33 - array( 34 - 'title' => array( 35 - nonempty(basename($drequest->getPath()), '/'), 36 - $drequest->getRepository()->getDisplayName(), 37 - ), 38 - )); 39 - } 40 - 41 - private function renderSearchResults() { 42 - $drequest = $this->getDiffusionRequest(); 43 - $repository = $drequest->getRepository(); 44 - $results = array(); 45 - 46 - $limit = 100; 47 - $page = $this->getRequest()->getInt('page', 0); 48 - $pager = new PHUIPagerView(); 49 - $pager->setPageSize($limit); 50 - $pager->setOffset($page); 51 - $pager->setURI($this->getRequest()->getRequestURI(), 'page'); 52 - 53 - $search_mode = null; 54 - 55 - switch ($repository->getVersionControlSystem()) { 56 - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 57 - $results = array(); 58 - break; 59 - default: 60 - if (strlen($this->getRequest()->getStr('grep'))) { 61 - $search_mode = 'grep'; 62 - $query_string = $this->getRequest()->getStr('grep'); 63 - $results = $this->callConduitWithDiffusionRequest( 64 - 'diffusion.searchquery', 65 - array( 66 - 'grep' => $query_string, 67 - 'commit' => $drequest->getStableCommit(), 68 - 'path' => $drequest->getPath(), 69 - 'limit' => $limit + 1, 70 - 'offset' => $page, 71 - )); 72 - } else { // Filename search. 73 - $search_mode = 'find'; 74 - $query_string = $this->getRequest()->getStr('find'); 75 - $results = $this->callConduitWithDiffusionRequest( 76 - 'diffusion.querypaths', 77 - array( 78 - 'pattern' => $query_string, 79 - 'commit' => $drequest->getStableCommit(), 80 - 'path' => $drequest->getPath(), 81 - 'limit' => $limit + 1, 82 - 'offset' => $page, 83 - )); 84 - } 85 - break; 86 - } 87 - 88 - $results = $pager->sliceResults($results); 89 - 90 - if ($search_mode == 'grep') { 91 - $table = $this->renderGrepResults($results, $query_string); 92 - $header = pht( 93 - 'File content matching "%s" under "%s"', 94 - $query_string, 95 - nonempty($drequest->getPath(), '/')); 96 - } else { 97 - $table = $this->renderFindResults($results); 98 - $header = pht( 99 - 'Paths matching "%s" under "%s"', 100 - $query_string, 101 - nonempty($drequest->getPath(), '/')); 102 - } 103 - 104 - $box = id(new PHUIObjectBoxView()) 105 - ->setHeaderText($header) 106 - ->setTable($table); 107 - 108 - $pager_box = id(new PHUIBoxView()) 109 - ->addMargin(PHUI::MARGIN_LARGE) 110 - ->appendChild($pager); 111 - 112 - return array($box, $pager_box); 113 - } 114 - 115 - private function renderGrepResults(array $results, $pattern) { 116 - $drequest = $this->getDiffusionRequest(); 117 - 118 - require_celerity_resource('phabricator-search-results-css'); 119 - 120 - $rows = array(); 121 - foreach ($results as $result) { 122 - list($path, $line, $string) = $result; 123 - 124 - $href = $drequest->generateURI(array( 125 - 'action' => 'browse', 126 - 'path' => $path, 127 - 'line' => $line, 128 - )); 129 - 130 - $matches = null; 131 - $count = @preg_match_all( 132 - '('.$pattern.')u', 133 - $string, 134 - $matches, 135 - PREG_OFFSET_CAPTURE); 136 - 137 - if (!$count) { 138 - $output = ltrim($string); 139 - } else { 140 - $output = array(); 141 - $cursor = 0; 142 - $length = strlen($string); 143 - foreach ($matches[0] as $match) { 144 - $offset = $match[1]; 145 - if ($cursor != $offset) { 146 - $output[] = array( 147 - 'text' => substr($string, $cursor, $offset), 148 - 'highlight' => false, 149 - ); 150 - } 151 - $output[] = array( 152 - 'text' => $match[0], 153 - 'highlight' => true, 154 - ); 155 - $cursor = $offset + strlen($match[0]); 156 - } 157 - if ($cursor != $length) { 158 - $output[] = array( 159 - 'text' => substr($string, $cursor), 160 - 'highlight' => false, 161 - ); 162 - } 163 - 164 - if ($output) { 165 - $output[0]['text'] = ltrim($output[0]['text']); 166 - } 167 - 168 - foreach ($output as $key => $segment) { 169 - if ($segment['highlight']) { 170 - $output[$key] = phutil_tag('strong', array(), $segment['text']); 171 - } else { 172 - $output[$key] = $segment['text']; 173 - } 174 - } 175 - } 176 - 177 - $string = phutil_tag( 178 - 'pre', 179 - array('class' => 'PhabricatorMonospaced phui-source-fragment'), 180 - $output); 181 - 182 - $path = Filesystem::readablePath($path, $drequest->getPath()); 183 - 184 - $rows[] = array( 185 - phutil_tag('a', array('href' => $href), $path), 186 - $line, 187 - $string, 188 - ); 189 - } 190 - 191 - $table = id(new AphrontTableView($rows)) 192 - ->setClassName('remarkup-code') 193 - ->setHeaders(array(pht('Path'), pht('Line'), pht('String'))) 194 - ->setColumnClasses(array('', 'n', 'wide')) 195 - ->setNoDataString( 196 - pht( 197 - 'The pattern you searched for was not found in the content of any '. 198 - 'files.')); 199 - 200 - return $table; 201 - } 202 - 203 - private function renderFindResults(array $results) { 204 - $drequest = $this->getDiffusionRequest(); 205 - 206 - $rows = array(); 207 - foreach ($results as $result) { 208 - $href = $drequest->generateURI(array( 209 - 'action' => 'browse', 210 - 'path' => $result, 211 - )); 212 - 213 - $readable = Filesystem::readablePath($result, $drequest->getPath()); 214 - 215 - $rows[] = array( 216 - phutil_tag('a', array('href' => $href), $readable), 217 - ); 218 - } 219 - 220 - $table = id(new AphrontTableView($rows)) 221 - ->setHeaders(array(pht('Path'))) 222 - ->setColumnClasses(array('wide')) 223 - ->setNoDataString( 224 - pht( 225 - 'The pattern you searched for did not match the names of any '. 226 - 'files.')); 227 - 228 - return $table; 229 - } 230 - 231 - }