[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 abstract class DifferentialChangesetRenderer { 4 5 private $user; 6 private $changeset; 7 private $renderingReference; 8 private $renderPropertyChangeHeader; 9 private $isTopLevel; 10 private $hunkStartLines; 11 private $oldLines; 12 private $newLines; 13 private $oldComments; 14 private $newComments; 15 private $oldChangesetID; 16 private $newChangesetID; 17 private $oldAttachesToNewFile; 18 private $newAttachesToNewFile; 19 private $highlightOld = array(); 20 private $highlightNew = array(); 21 private $codeCoverage; 22 private $handles; 23 private $markupEngine; 24 private $oldRender; 25 private $newRender; 26 private $originalOld; 27 private $originalNew; 28 private $gaps; 29 private $mask; 30 private $depths; 31 private $originalCharacterEncoding; 32 33 private $oldFile = false; 34 private $newFile = false; 35 36 public function setOriginalCharacterEncoding($original_character_encoding) { 37 $this->originalCharacterEncoding = $original_character_encoding; 38 return $this; 39 } 40 41 public function getOriginalCharacterEncoding() { 42 return $this->originalCharacterEncoding; 43 } 44 45 public function setDepths($depths) { 46 $this->depths = $depths; 47 return $this; 48 } 49 protected function getDepths() { 50 return $this->depths; 51 } 52 53 public function setMask($mask) { 54 $this->mask = $mask; 55 return $this; 56 } 57 protected function getMask() { 58 return $this->mask; 59 } 60 61 public function setGaps($gaps) { 62 $this->gaps = $gaps; 63 return $this; 64 } 65 protected function getGaps() { 66 return $this->gaps; 67 } 68 69 public function attachOldFile(PhabricatorFile $old = null) { 70 $this->oldFile = $old; 71 return $this; 72 } 73 74 public function getOldFile() { 75 if ($this->oldFile === false) { 76 throw new PhabricatorDataNotAttachedException($this); 77 } 78 return $this->oldFile; 79 } 80 81 public function hasOldFile() { 82 return (bool)$this->oldFile; 83 } 84 85 public function attachNewFile(PhabricatorFile $new = null) { 86 $this->newFile = $new; 87 return $this; 88 } 89 90 public function getNewFile() { 91 if ($this->newFile === false) { 92 throw new PhabricatorDataNotAttachedException($this); 93 } 94 return $this->newFile; 95 } 96 97 public function hasNewFile() { 98 return (bool)$this->newFile; 99 } 100 101 public function setOriginalNew($original_new) { 102 $this->originalNew = $original_new; 103 return $this; 104 } 105 protected function getOriginalNew() { 106 return $this->originalNew; 107 } 108 109 public function setOriginalOld($original_old) { 110 $this->originalOld = $original_old; 111 return $this; 112 } 113 protected function getOriginalOld() { 114 return $this->originalOld; 115 } 116 117 public function setNewRender($new_render) { 118 $this->newRender = $new_render; 119 return $this; 120 } 121 protected function getNewRender() { 122 return $this->newRender; 123 } 124 125 public function setOldRender($old_render) { 126 $this->oldRender = $old_render; 127 return $this; 128 } 129 protected function getOldRender() { 130 return $this->oldRender; 131 } 132 133 public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) { 134 $this->markupEngine = $markup_engine; 135 return $this; 136 } 137 public function getMarkupEngine() { 138 return $this->markupEngine; 139 } 140 141 public function setHandles(array $handles) { 142 assert_instances_of($handles, 'PhabricatorObjectHandle'); 143 $this->handles = $handles; 144 return $this; 145 } 146 protected function getHandles() { 147 return $this->handles; 148 } 149 150 public function setCodeCoverage($code_coverage) { 151 $this->codeCoverage = $code_coverage; 152 return $this; 153 } 154 protected function getCodeCoverage() { 155 return $this->codeCoverage; 156 } 157 158 public function setHighlightNew($highlight_new) { 159 $this->highlightNew = $highlight_new; 160 return $this; 161 } 162 protected function getHighlightNew() { 163 return $this->highlightNew; 164 } 165 166 public function setHighlightOld($highlight_old) { 167 $this->highlightOld = $highlight_old; 168 return $this; 169 } 170 protected function getHighlightOld() { 171 return $this->highlightOld; 172 } 173 174 public function setNewAttachesToNewFile($attaches) { 175 $this->newAttachesToNewFile = $attaches; 176 return $this; 177 } 178 protected function getNewAttachesToNewFile() { 179 return $this->newAttachesToNewFile; 180 } 181 182 public function setOldAttachesToNewFile($attaches) { 183 $this->oldAttachesToNewFile = $attaches; 184 return $this; 185 } 186 protected function getOldAttachesToNewFile() { 187 return $this->oldAttachesToNewFile; 188 } 189 190 public function setNewChangesetID($new_changeset_id) { 191 $this->newChangesetID = $new_changeset_id; 192 return $this; 193 } 194 protected function getNewChangesetID() { 195 return $this->newChangesetID; 196 } 197 198 public function setOldChangesetID($old_changeset_id) { 199 $this->oldChangesetID = $old_changeset_id; 200 return $this; 201 } 202 protected function getOldChangesetID() { 203 return $this->oldChangesetID; 204 } 205 206 public function setNewComments(array $new_comments) { 207 foreach ($new_comments as $line_number => $comments) { 208 assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 209 } 210 $this->newComments = $new_comments; 211 return $this; 212 } 213 protected function getNewComments() { 214 return $this->newComments; 215 } 216 217 public function setOldComments(array $old_comments) { 218 foreach ($old_comments as $line_number => $comments) { 219 assert_instances_of($comments, 'PhabricatorInlineCommentInterface'); 220 } 221 $this->oldComments = $old_comments; 222 return $this; 223 } 224 protected function getOldComments() { 225 return $this->oldComments; 226 } 227 228 public function setNewLines(array $new_lines) { 229 $this->newLines = $new_lines; 230 return $this; 231 } 232 protected function getNewLines() { 233 return $this->newLines; 234 } 235 236 public function setOldLines(array $old_lines) { 237 $this->oldLines = $old_lines; 238 return $this; 239 } 240 protected function getOldLines() { 241 return $this->oldLines; 242 } 243 244 public function setHunkStartLines(array $hunk_start_lines) { 245 $this->hunkStartLines = $hunk_start_lines; 246 return $this; 247 } 248 249 protected function getHunkStartLines() { 250 return $this->hunkStartLines; 251 } 252 253 public function setUser(PhabricatorUser $user) { 254 $this->user = $user; 255 return $this; 256 } 257 protected function getUser() { 258 return $this->user; 259 } 260 261 public function setChangeset(DifferentialChangeset $changeset) { 262 $this->changeset = $changeset; 263 return $this; 264 } 265 protected function getChangeset() { 266 return $this->changeset; 267 } 268 269 public function setRenderingReference($rendering_reference) { 270 $this->renderingReference = $rendering_reference; 271 return $this; 272 } 273 protected function getRenderingReference() { 274 return $this->renderingReference; 275 } 276 277 public function setRenderPropertyChangeHeader($should_render) { 278 $this->renderPropertyChangeHeader = $should_render; 279 return $this; 280 } 281 private function shouldRenderPropertyChangeHeader() { 282 return $this->renderPropertyChangeHeader; 283 } 284 285 public function setIsTopLevel($is) { 286 $this->isTopLevel = $is; 287 return $this; 288 } 289 private function getIsTopLevel() { 290 return $this->isTopLevel; 291 } 292 293 final public function renderChangesetTable($content) { 294 $props = null; 295 if ($this->shouldRenderPropertyChangeHeader()) { 296 $props = $this->renderPropertyChangeHeader(); 297 } 298 299 $notice = null; 300 if ($this->getIsTopLevel()) { 301 $force = (!$content && !$props); 302 $notice = $this->renderChangeTypeHeader($force); 303 } 304 305 $result = $notice.$props.$content; 306 307 // TODO: Let the user customize their tab width / display style. 308 // TODO: We should possibly post-process "\r" as well. 309 // TODO: Both these steps should happen earlier. 310 $result = str_replace("\t", ' ', $result); 311 312 return phutil_safe_html($result); 313 } 314 315 abstract public function isOneUpRenderer(); 316 abstract public function renderTextChange( 317 $range_start, 318 $range_len, 319 $rows); 320 abstract public function renderFileChange( 321 $old = null, 322 $new = null, 323 $id = 0, 324 $vs = 0); 325 326 abstract protected function renderChangeTypeHeader($force); 327 328 protected function didRenderChangesetTableContents($contents) { 329 return $contents; 330 } 331 332 /** 333 * Render a "shield" over the diff, with a message like "This file is 334 * generated and does not need to be reviewed." or "This file was completely 335 * deleted." This UI element hides unimportant text so the reviewer doesn't 336 * need to scroll past it. 337 * 338 * The shield includes a link to view the underlying content. This link 339 * may force certain rendering modes when the link is clicked: 340 * 341 * - `"default"`: Render the diff normally, as though it was not 342 * shielded. This is the default and appropriate if the underlying 343 * diff is a normal change, but was hidden for reasons of not being 344 * important (e.g., generated code). 345 * - `"text"`: Force the text to be shown. This is probably only relevant 346 * when a file is not changed. 347 * - `"whitespace"`: Force the text to be shown, and the diff to be 348 * rendered with all whitespace shown. This is probably only relevant 349 * when a file is changed only by altering whitespace. 350 * - `"none"`: Don't show the link (e.g., text not available). 351 * 352 * @param string Message explaining why the diff is hidden. 353 * @param string|null Force mode, see above. 354 * @return string Shield markup. 355 */ 356 abstract public function renderShield($message, $force = 'default'); 357 358 abstract protected function renderPropertyChangeHeader(); 359 360 protected function buildPrimitives($range_start, $range_len) { 361 $primitives = array(); 362 363 $hunk_starts = $this->getHunkStartLines(); 364 365 $mask = $this->getMask(); 366 $gaps = $this->getGaps(); 367 368 $old = $this->getOldLines(); 369 $new = $this->getNewLines(); 370 $old_render = $this->getOldRender(); 371 $new_render = $this->getNewRender(); 372 $old_comments = $this->getOldComments(); 373 $new_comments = $this->getNewComments(); 374 375 $size = count($old); 376 for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { 377 if (empty($mask[$ii])) { 378 list($top, $len) = array_pop($gaps); 379 $primitives[] = array( 380 'type' => 'context', 381 'top' => $top, 382 'len' => $len, 383 ); 384 385 $ii += ($len - 1); 386 continue; 387 } 388 389 $ospec = array( 390 'type' => 'old', 391 'htype' => null, 392 'cursor' => $ii, 393 'line' => null, 394 'oline' => null, 395 'render' => null, 396 ); 397 398 $nspec = array( 399 'type' => 'new', 400 'htype' => null, 401 'cursor' => $ii, 402 'line' => null, 403 'oline' => null, 404 'render' => null, 405 'copy' => null, 406 'coverage' => null, 407 ); 408 409 if (isset($old[$ii])) { 410 $ospec['line'] = (int)$old[$ii]['line']; 411 $nspec['oline'] = (int)$old[$ii]['line']; 412 $ospec['htype'] = $old[$ii]['type']; 413 if (isset($old_render[$ii])) { 414 $ospec['render'] = $old_render[$ii]; 415 } 416 } 417 418 if (isset($new[$ii])) { 419 $nspec['line'] = (int)$new[$ii]['line']; 420 $ospec['oline'] = (int)$new[$ii]['line']; 421 $nspec['htype'] = $new[$ii]['type']; 422 if (isset($new_render[$ii])) { 423 $nspec['render'] = $new_render[$ii]; 424 } 425 } 426 427 if (isset($hunk_starts[$ospec['line']])) { 428 $primitives[] = array( 429 'type' => 'no-context', 430 ); 431 } 432 433 $primitives[] = $ospec; 434 $primitives[] = $nspec; 435 436 if ($ospec['line'] !== null && isset($old_comments[$ospec['line']])) { 437 foreach ($old_comments[$ospec['line']] as $comment) { 438 $primitives[] = array( 439 'type' => 'inline', 440 'comment' => $comment, 441 'right' => false, 442 ); 443 } 444 } 445 446 if ($nspec['line'] !== null && isset($new_comments[$nspec['line']])) { 447 foreach ($new_comments[$nspec['line']] as $comment) { 448 $primitives[] = array( 449 'type' => 'inline', 450 'comment' => $comment, 451 'right' => true, 452 ); 453 } 454 } 455 456 if ($hunk_starts && ($ii == $size - 1)) { 457 $primitives[] = array( 458 'type' => 'no-context', 459 ); 460 } 461 } 462 463 if ($this->isOneUpRenderer()) { 464 $primitives = $this->processPrimitivesForOneUp($primitives); 465 } 466 467 return $primitives; 468 } 469 470 private function processPrimitivesForOneUp(array $primitives) { 471 // Primitives come out of buildPrimitives() in two-up format, because it 472 // is the most general, flexible format. To put them into one-up format, 473 // we need to filter and reorder them. In particular: 474 // 475 // - We discard unchanged lines in the old file; in one-up format, we 476 // render them only once. 477 // - We group contiguous blocks of old-modified and new-modified lines, so 478 // they render in "block of old, block of new" order instead of 479 // alternating old and new lines. 480 481 $out = array(); 482 483 $old_buf = array(); 484 $new_buf = array(); 485 foreach ($primitives as $primitive) { 486 $type = $primitive['type']; 487 488 if ($type == 'old') { 489 if (!$primitive['htype']) { 490 // This is a line which appears in both the old file and the new 491 // file, or the spacer corresponding to a line added in the new file. 492 // Ignore it when rendering a one-up diff. 493 continue; 494 } 495 if ($new_buf) { 496 $out[] = $new_buf; 497 $new_buf = array(); 498 } 499 $old_buf[] = $primitive; 500 } else if ($type == 'new') { 501 if ($primitive['line'] === null) { 502 // This is an empty spacer corresponding to a line removed from the 503 // old file. Ignore it when rendering a one-up diff. 504 continue; 505 } 506 if ($old_buf) { 507 $out[] = $old_buf; 508 $old_buf = array(); 509 } 510 $new_buf[] = $primitive; 511 } else if ($type == 'context' || $type == 'no-context') { 512 $out[] = $old_buf; 513 $out[] = $new_buf; 514 $old_buf = array(); 515 $new_buf = array(); 516 $out[] = array($primitive); 517 } else if ($type == 'inline') { 518 $out[] = $old_buf; 519 $out[] = $new_buf; 520 $old_buf = array(); 521 $new_buf = array(); 522 523 $out[] = array($primitive); 524 } else { 525 throw new Exception("Unknown primitive type '{$primitive}'!"); 526 } 527 } 528 529 $out[] = $old_buf; 530 $out[] = $new_buf; 531 $out = array_mergev($out); 532 533 return $out; 534 } 535 536 protected function getChangesetProperties($changeset) { 537 $old = $changeset->getOldProperties(); 538 $new = $changeset->getNewProperties(); 539 540 // When adding files, don't show the uninteresting 644 filemode change. 541 if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD && 542 $new == array('unix:filemode' => '100644')) { 543 unset($new['unix:filemode']); 544 } 545 546 // Likewise when removing files. 547 if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE && 548 $old == array('unix:filemode' => '100644')) { 549 unset($old['unix:filemode']); 550 } 551 552 if ($this->hasOldFile()) { 553 $file = $this->getOldFile(); 554 if ($file->getImageWidth()) { 555 $dimensions = $file->getImageWidth().'x'.$file->getImageHeight(); 556 $old['file:dimensions'] = $dimensions; 557 } 558 $old['file:mimetype'] = $file->getMimeType(); 559 $old['file:size'] = phutil_format_bytes($file->getByteSize()); 560 } 561 562 if ($this->hasNewFile()) { 563 $file = $this->getNewFile(); 564 if ($file->getImageWidth()) { 565 $dimensions = $file->getImageWidth().'x'.$file->getImageHeight(); 566 $new['file:dimensions'] = $dimensions; 567 } 568 $new['file:mimetype'] = $file->getMimeType(); 569 $new['file:size'] = phutil_format_bytes($file->getByteSize()); 570 } 571 572 return array($old, $new); 573 } 574 575 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |