@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
at recaptime-dev/main 645 lines 17 kB view raw
1<?php 2 3final class DifferentialChangesetTwoUpRenderer 4 extends DifferentialChangesetHTMLRenderer { 5 6 private $newOffsetMap; 7 8 public function isOneUpRenderer() { 9 return false; 10 } 11 12 protected function getRendererTableClass() { 13 return 'diff-2up'; 14 } 15 16 public function getRendererKey() { 17 return '2up'; 18 } 19 20 protected function renderColgroup() { 21 return phutil_tag('colgroup', array(), array( 22 phutil_tag('col', array('class' => 'num')), 23 phutil_tag('col', array('class' => 'left')), 24 phutil_tag('col', array('class' => 'num')), 25 phutil_tag('col', array('class' => 'copy')), 26 phutil_tag('col', array('class' => 'right')), 27 phutil_tag('col', array('class' => 'cov')), 28 )); 29 } 30 31 public function renderTextChange( 32 $range_start, 33 $range_len, 34 $rows) { 35 36 $hunk_starts = $this->getHunkStartLines(); 37 38 $context_not_available = null; 39 if ($hunk_starts) { 40 $context_not_available = javelin_tag( 41 'tr', 42 array( 43 'sigil' => 'context-target', 44 ), 45 phutil_tag( 46 'td', 47 array( 48 'colspan' => 6, 49 'class' => 'show-more', 50 ), 51 pht('Context not available.'))); 52 } 53 54 $html = array(); 55 56 $old_lines = $this->getOldLines(); 57 $new_lines = $this->getNewLines(); 58 $gaps = $this->getGaps(); 59 $reference = $this->getRenderingReference(); 60 61 list($left_prefix, $right_prefix) = $this->getLineIDPrefixes(); 62 63 $changeset = $this->getChangeset(); 64 $copy_lines = idx($changeset->getMetadata(), 'copy:lines', array()); 65 $highlight_old = $this->getHighlightOld(); 66 $highlight_new = $this->getHighlightNew(); 67 $old_render = $this->getOldRender(); 68 $new_render = $this->getNewRender(); 69 $original_left = $this->getOriginalOld(); 70 $original_right = $this->getOriginalNew(); 71 $mask = $this->getMask(); 72 73 $scope_engine = $this->getScopeEngine(); 74 $offset_map = null; 75 $depth_only = $this->getDepthOnlyLines(); 76 77 for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { 78 if (empty($mask[$ii])) { 79 // If we aren't going to show this line, we've just entered a gap. 80 // Pop information about the next gap off the $gaps stack and render 81 // an appropriate "Show more context" element. This branch eventually 82 // increments $ii by the entire size of the gap and then continues 83 // the loop. 84 $gap = array_pop($gaps); 85 $top = $gap[0]; 86 $len = $gap[1]; 87 88 $contents = $this->renderShowContextLinks($top, $len, $rows); 89 90 $is_last_block = false; 91 if ($ii + $len >= $rows) { 92 $is_last_block = true; 93 } 94 95 $context_text = null; 96 $context_line = null; 97 if (!$is_last_block && $scope_engine) { 98 $target_line = $new_lines[$ii + $len]['line']; 99 $context_line = $scope_engine->getScopeStart($target_line); 100 if ($context_line !== null) { 101 // The scope engine returns a line number in the file. We need 102 // to map that back to a display offset in the diff. 103 if (!$offset_map) { 104 $offset_map = $this->getNewLineToOffsetMap(); 105 } 106 $offset = $offset_map[$context_line]; 107 $context_text = $new_render[$offset]; 108 } 109 } 110 111 $container = javelin_tag( 112 'tr', 113 array( 114 'sigil' => 'context-target', 115 ), 116 array( 117 phutil_tag( 118 'td', 119 array( 120 'class' => 'show-context-line n left-context', 121 )), 122 phutil_tag( 123 'td', 124 array( 125 'class' => 'show-more', 126 ), 127 $contents), 128 phutil_tag( 129 'td', 130 array( 131 'class' => 'show-context-line n', 132 'data-n' => $context_line, 133 )), 134 phutil_tag( 135 'td', 136 array( 137 'colspan' => 3, 138 'class' => 'show-context', 139 ), 140 // TODO: [HTML] Escaping model here isn't ideal. 141 phutil_safe_html($context_text)), 142 )); 143 144 $html[] = $container; 145 146 $ii += ($len - 1); 147 continue; 148 } 149 150 $o_num = null; 151 $o_classes = ''; 152 $o_text = null; 153 if (isset($old_lines[$ii])) { 154 $o_num = $old_lines[$ii]['line']; 155 $o_text = isset($old_render[$ii]) ? $old_render[$ii] : null; 156 if ($old_lines[$ii]['type']) { 157 if ($old_lines[$ii]['type'] == '\\') { 158 $o_text = $old_lines[$ii]['text']; 159 $o_class = 'comment'; 160 } else if ($original_left && !isset($highlight_old[$o_num])) { 161 $o_class = 'old-rebase'; 162 } else if (empty($new_lines[$ii])) { 163 $o_class = 'old old-full'; 164 } else { 165 if (isset($depth_only[$ii])) { 166 if ($depth_only[$ii] == '>') { 167 // When a line has depth-only change, we only highlight the 168 // left side of the diff if the depth is decreasing. When the 169 // depth is increasing, the ">>" marker on the right hand side 170 // of the diff generally provides enough visibility on its own. 171 172 $o_class = ''; 173 } else { 174 $o_class = 'old'; 175 } 176 } else { 177 $o_class = 'old'; 178 } 179 } 180 $o_classes = $o_class; 181 } 182 } 183 184 $n_copy = hsprintf('<td class="copy" />'); 185 $n_cov = null; 186 $n_colspan = 2; 187 $n_classes = ''; 188 $n_num = null; 189 $n_text = null; 190 191 if (isset($new_lines[$ii])) { 192 $n_num = $new_lines[$ii]['line']; 193 $n_text = isset($new_render[$ii]) ? $new_render[$ii] : null; 194 $coverage = $this->getCodeCoverage(); 195 196 if ($coverage !== null) { 197 if (empty($coverage[$n_num - 1])) { 198 $cov_class = 'N'; 199 } else { 200 $cov_class = $coverage[$n_num - 1]; 201 } 202 $cov_class = 'cov-'.$cov_class; 203 $n_cov = phutil_tag('td', array('class' => "cov {$cov_class}")); 204 $n_colspan--; 205 } 206 207 if ($new_lines[$ii]['type']) { 208 if ($new_lines[$ii]['type'] == '\\') { 209 $n_text = $new_lines[$ii]['text']; 210 $n_class = 'comment'; 211 } else if ($original_right && !isset($highlight_new[$n_num])) { 212 $n_class = 'new-rebase'; 213 } else if (empty($old_lines[$ii])) { 214 $n_class = 'new new-full'; 215 } else { 216 // When a line has a depth-only change, never highlight it on 217 // the right side. The ">>" marker generally provides enough 218 // visibility on its own for indent depth increases, and the left 219 // side is still highlighted for indent depth decreases. 220 221 if (isset($depth_only[$ii])) { 222 $n_class = ''; 223 } else { 224 $n_class = 'new'; 225 } 226 } 227 $n_classes = $n_class; 228 229 $not_copied = 230 // If this line only changed depth, copy markers are pointless. 231 (!isset($copy_lines[$n_num])) || 232 (isset($depth_only[$ii])) || 233 ($new_lines[$ii]['type'] == '\\'); 234 235 if ($not_copied) { 236 $n_copy = phutil_tag('td', array('class' => 'copy')); 237 } else { 238 list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num]; 239 $title = ($orig_type == '-' ? 'Moved' : 'Copied').' from '; 240 if ($orig_file == '') { 241 $title .= "line {$orig_line}"; 242 } else { 243 $title .= 244 basename($orig_file). 245 ":{$orig_line} in dir ". 246 dirname('/'.$orig_file); 247 } 248 $class = ($orig_type == '-' ? 'new-move' : 'new-copy'); 249 $n_copy = javelin_tag( 250 'td', 251 array( 252 'meta' => array( 253 'msg' => $title, 254 ), 255 'class' => 'copy '.$class, 256 )); 257 } 258 } 259 } 260 261 if ($o_num && isset($hunk_starts[$o_num])) { 262 $html[] = $context_not_available; 263 } 264 265 if ($o_num && $left_prefix) { 266 $o_id = $left_prefix.$o_num; 267 } else { 268 $o_id = null; 269 } 270 271 if ($n_num && $right_prefix) { 272 $n_id = $right_prefix.$n_num; 273 } else { 274 $n_id = null; 275 } 276 277 $old_comments = $this->getOldComments(); 278 $new_comments = $this->getNewComments(); 279 $scaffolds = array(); 280 281 if ($o_num && isset($old_comments[$o_num])) { 282 foreach ($old_comments[$o_num] as $comment) { 283 $inline = $this->buildInlineComment( 284 $comment, 285 $on_right = false); 286 $scaffold = $this->getRowScaffoldForInline($inline); 287 288 if ($n_num && isset($new_comments[$n_num])) { 289 foreach ($new_comments[$n_num] as $key => $new_comment) { 290 if ($comment->isCompatible($new_comment)) { 291 $companion = $this->buildInlineComment( 292 $new_comment, 293 $on_right = true); 294 295 $scaffold->addInlineView($companion); 296 unset($new_comments[$n_num][$key]); 297 break; 298 } 299 } 300 } 301 302 303 $scaffolds[] = $scaffold; 304 } 305 } 306 307 if ($n_num && isset($new_comments[$n_num])) { 308 foreach ($new_comments[$n_num] as $comment) { 309 $inline = $this->buildInlineComment( 310 $comment, 311 $on_right = true); 312 313 $scaffolds[] = $this->getRowScaffoldForInline($inline); 314 } 315 } 316 317 $old_number = phutil_tag( 318 'td', 319 array( 320 'id' => $o_id, 321 'class' => $o_classes.' n', 322 'data-n' => $o_num, 323 )); 324 325 $new_number = phutil_tag( 326 'td', 327 array( 328 'id' => $n_id, 329 'class' => $n_classes.' n', 330 'data-n' => $n_num, 331 )); 332 333 $html[] = phutil_tag('tr', array(), array( 334 $old_number, 335 phutil_tag( 336 'td', 337 array( 338 'class' => $o_classes, 339 'data-copy-mode' => 'copy-l', 340 ), 341 $o_text), 342 $new_number, 343 $n_copy, 344 phutil_tag( 345 'td', 346 array( 347 'class' => $n_classes, 348 'colspan' => $n_colspan, 349 'data-copy-mode' => 'copy-r', 350 ), 351 $n_text), 352 $n_cov, 353 )); 354 355 if ($context_not_available && ($ii == $rows - 1)) { 356 $html[] = $context_not_available; 357 } 358 359 foreach ($scaffolds as $scaffold) { 360 $html[] = $scaffold; 361 } 362 } 363 364 return $this->wrapChangeInTable(phutil_implode_html('', $html)); 365 } 366 367 public function renderDocumentEngineBlocks( 368 PhabricatorDocumentEngineBlocks $block_list, 369 $old_changeset_key, 370 $new_changeset_key) { 371 372 $engine = $this->getDocumentEngine(); 373 374 $old_ref = null; 375 $new_ref = null; 376 $refs = $block_list->getDocumentRefs(); 377 if ($refs) { 378 list($old_ref, $new_ref) = $refs; 379 } 380 381 $old_comments = $this->getOldComments(); 382 $new_comments = $this->getNewComments(); 383 384 $rows = array(); 385 $gap = array(); 386 $in_gap = false; 387 388 // NOTE: The generated layout is affected by range constraints, and may 389 // represent only a slice of the document. 390 391 $layout = $block_list->newTwoUpLayout(); 392 $available_count = $block_list->getLayoutAvailableRowCount(); 393 394 foreach ($layout as $idx => $row) { 395 list($old, $new) = $row; 396 397 if ($old) { 398 $old_key = $old->getBlockKey(); 399 $is_visible = $old->getIsVisible(); 400 } else { 401 $old_key = null; 402 } 403 404 if ($new) { 405 $new_key = $new->getBlockKey(); 406 $is_visible = $new->getIsVisible(); 407 } else { 408 $new_key = null; 409 } 410 411 if (!$is_visible) { 412 if (!$in_gap) { 413 $in_gap = true; 414 } 415 $gap[$idx] = $row; 416 continue; 417 } 418 419 if ($in_gap) { 420 $in_gap = false; 421 $rows[] = $this->renderDocumentEngineGap( 422 $gap, 423 $available_count); 424 $gap = array(); 425 } 426 427 if ($old) { 428 $is_rem = ($old->getDifferenceType() === '-'); 429 } else { 430 $is_rem = false; 431 } 432 433 if ($new) { 434 $is_add = ($new->getDifferenceType() === '+'); 435 } else { 436 $is_add = false; 437 } 438 439 if ($is_rem && $is_add) { 440 $block_diff = $engine->newBlockDiffViews( 441 $old_ref, 442 $old, 443 $new_ref, 444 $new); 445 446 $old_content = $block_diff->getOldContent(); 447 $new_content = $block_diff->getNewContent(); 448 449 $old_classes = $block_diff->getOldClasses(); 450 $new_classes = $block_diff->getNewClasses(); 451 } else { 452 $old_classes = array(); 453 $new_classes = array(); 454 455 if ($old) { 456 $old_content = $engine->newBlockContentView( 457 $old_ref, 458 $old); 459 460 if ($is_rem) { 461 $old_classes[] = 'old'; 462 $old_classes[] = 'old-full'; 463 } 464 } else { 465 $old_content = null; 466 } 467 468 if ($new) { 469 $new_content = $engine->newBlockContentView( 470 $new_ref, 471 $new); 472 473 if ($is_add) { 474 $new_classes[] = 'new'; 475 $new_classes[] = 'new-full'; 476 } 477 } else { 478 $new_content = null; 479 } 480 } 481 482 $old_classes[] = 'diff-flush'; 483 $old_classes = implode(' ', $old_classes); 484 485 $new_classes[] = 'diff-flush'; 486 $new_classes = implode(' ', $new_classes); 487 488 $old_inline_rows = array(); 489 if ($old_key !== null) { 490 $old_inlines = idx($old_comments, $old_key, array()); 491 foreach ($old_inlines as $inline) { 492 $inline = $this->buildInlineComment( 493 $inline, 494 $on_right = false); 495 $old_inline_rows[] = $this->getRowScaffoldForInline($inline); 496 } 497 } 498 499 $new_inline_rows = array(); 500 if ($new_key !== null) { 501 $new_inlines = idx($new_comments, $new_key, array()); 502 foreach ($new_inlines as $inline) { 503 $inline = $this->buildInlineComment( 504 $inline, 505 $on_right = true); 506 $new_inline_rows[] = $this->getRowScaffoldForInline($inline); 507 } 508 } 509 510 if ($old_content === null) { 511 $old_id = null; 512 } else { 513 $old_id = "C{$old_changeset_key}OL{$old_key}"; 514 } 515 516 $old_line_cell = phutil_tag( 517 'td', 518 array( 519 'id' => $old_id, 520 'data-n' => $old_key, 521 'class' => 'n', 522 )); 523 524 $old_content_cell = phutil_tag( 525 'td', 526 array( 527 'class' => $old_classes, 528 'data-copy-mode' => 'copy-l', 529 ), 530 $old_content); 531 532 if ($new_content === null) { 533 $new_id = null; 534 } else { 535 $new_id = "C{$new_changeset_key}NL{$new_key}"; 536 } 537 538 $new_line_cell = phutil_tag( 539 'td', 540 array( 541 'id' => $new_id, 542 'data-n' => $new_key, 543 'class' => 'n', 544 )); 545 546 $copy_gutter = phutil_tag( 547 'td', 548 array( 549 'class' => 'copy', 550 )); 551 552 $new_content_cell = phutil_tag( 553 'td', 554 array( 555 'class' => $new_classes, 556 'colspan' => '2', 557 'data-copy-mode' => 'copy-r', 558 ), 559 $new_content); 560 561 $row_view = phutil_tag( 562 'tr', 563 array(), 564 array( 565 $old_line_cell, 566 $old_content_cell, 567 $new_line_cell, 568 $copy_gutter, 569 $new_content_cell, 570 )); 571 572 $rows[] = array( 573 $row_view, 574 $old_inline_rows, 575 $new_inline_rows, 576 ); 577 } 578 579 if ($in_gap) { 580 $rows[] = $this->renderDocumentEngineGap( 581 $gap, 582 $available_count); 583 } 584 585 $output = $this->wrapChangeInTable($rows); 586 587 return $this->renderChangesetTable($output); 588 } 589 590 public function getRowScaffoldForInline(PHUIDiffInlineCommentView $view) { 591 return id(new PHUIDiffTwoUpInlineCommentRowScaffold()) 592 ->addInlineView($view); 593 } 594 595 private function getNewLineToOffsetMap() { 596 if ($this->newOffsetMap === null) { 597 $new = $this->getNewLines(); 598 599 $map = array(); 600 foreach ($new as $offset => $new_line) { 601 if ($new_line === null) { 602 continue; 603 } 604 605 if ($new_line['line'] === null) { 606 continue; 607 } 608 609 $map[$new_line['line']] = $offset; 610 } 611 612 $this->newOffsetMap = $map; 613 } 614 615 return $this->newOffsetMap; 616 } 617 618 protected function getTableSigils() { 619 return array( 620 'intercept-copy', 621 ); 622 } 623 624 private function renderDocumentEngineGap(array $gap, $available_count) { 625 $content = $this->renderShowContextLinks( 626 head_key($gap), 627 count($gap), 628 $available_count, 629 $is_blocks = true); 630 631 return javelin_tag( 632 'tr', 633 array( 634 'sigil' => 'context-target', 635 ), 636 phutil_tag( 637 'td', 638 array( 639 'colspan' => 6, 640 'class' => 'show-more', 641 ), 642 $content)); 643 } 644 645}