@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 534 lines 12 kB view raw
1<?php 2 3final class PHUIDiffTableOfContentsListView extends AphrontView { 4 5 private $items = array(); 6 private $authorityPackages; 7 private $header; 8 private $infoView; 9 private $background; 10 private $bare; 11 12 private $components = array(); 13 14 public function addItem(PHUIDiffTableOfContentsItemView $item) { 15 $this->items[] = $item; 16 return $this; 17 } 18 19 /** 20 * @param array<PhabricatorOwnersPackage> $authority_packages 21 */ 22 public function setAuthorityPackages(array $authority_packages) { 23 assert_instances_of($authority_packages, PhabricatorOwnersPackage::class); 24 $this->authorityPackages = $authority_packages; 25 return $this; 26 } 27 28 public function getAuthorityPackages() { 29 return $this->authorityPackages; 30 } 31 32 public function setBackground($background) { 33 $this->background = $background; 34 return $this; 35 } 36 37 public function setHeader(PHUIHeaderView $header) { 38 $this->header = $header; 39 return $this; 40 } 41 42 public function setInfoView(PHUIInfoView $infoview) { 43 $this->infoView = $infoview; 44 return $this; 45 } 46 47 public function setBare($bare) { 48 $this->bare = $bare; 49 return $this; 50 } 51 52 public function getBare() { 53 return $this->bare; 54 } 55 56 public function render() { 57 $this->requireResource('differential-core-view-css'); 58 $this->requireResource('differential-table-of-contents-css'); 59 60 Javelin::initBehavior('phabricator-tooltips'); 61 62 if ($this->getAuthorityPackages()) { 63 $authority = mpull($this->getAuthorityPackages(), null, 'getPHID'); 64 } else { 65 $authority = array(); 66 } 67 68 $items = $this->items; 69 $viewer = $this->getViewer(); 70 71 $item_map = array(); 72 73 $vector_tree = new ArcanistDiffVectorTree(); 74 foreach ($items as $item) { 75 $item->setViewer($viewer); 76 77 $changeset = $item->getChangeset(); 78 79 $old_vector = $changeset->getOldStatePathVector(); 80 $new_vector = $changeset->getNewStatePathVector(); 81 82 $tree_vector = $this->newTreeVector($old_vector, $new_vector); 83 84 $item_map[implode("\n", $tree_vector)] = $item; 85 86 $vector_tree->addVector($tree_vector); 87 } 88 $node_list = $vector_tree->newDisplayList(); 89 90 $node_map = array(); 91 foreach ($node_list as $node) { 92 $path_vector = $node->getVector(); 93 $path_vector = implode("\n", $path_vector); 94 $node_map[$path_vector] = $node; 95 } 96 97 // Mark all nodes which contain at least one path which exists in the new 98 // state. Nodes we don't mark contain only deleted or moved files, so they 99 // can be rendered with a less-prominent style. 100 101 foreach ($node_map as $node_key => $node) { 102 $item = idx($item_map, $node_key); 103 104 if (!$item) { 105 continue; 106 } 107 108 $changeset = $item->getChangeset(); 109 if (!$changeset->getIsLowImportanceChangeset()) { 110 $node->setAncestralAttribute('important', true); 111 } 112 } 113 114 $any_packages = false; 115 $any_coverage = false; 116 $any_context = false; 117 118 $rows = array(); 119 $rowc = array(); 120 foreach ($node_map as $node_key => $node) { 121 $display_vector = $node->getDisplayVector(); 122 $item = idx($item_map, $node_key); 123 124 if ($item) { 125 $changeset = $item->getChangeset(); 126 $icon = $changeset->newFileTreeIcon(); 127 } else { 128 $changeset = null; 129 $icon = id(new PHUIIconView()) 130 ->setIcon('fa-folder-open-o grey'); 131 } 132 133 if ($node->getChildren()) { 134 $old_dir = true; 135 $new_dir = true; 136 } else { 137 // TODO: When properties are set on a directory in SVN directly, this 138 // might be incorrect. 139 $old_dir = false; 140 $new_dir = false; 141 } 142 143 $display_view = $this->newComponentView( 144 $icon, 145 $display_vector, 146 $old_dir, 147 $new_dir, 148 $item); 149 150 $depth = $node->getDisplayDepth(); 151 152 $style = sprintf('padding-left: %dpx;', $depth * 16); 153 154 if ($item) { 155 $packages = $item->renderPackages(); 156 } else { 157 $packages = null; 158 } 159 160 if ($packages) { 161 $any_packages = true; 162 } 163 164 if ($item) { 165 if ($item->getCoverage()) { 166 $any_coverage = true; 167 } 168 $coverage = $item->renderCoverage(); 169 $modified_coverage = $item->renderModifiedCoverage(); 170 } else { 171 $coverage = null; 172 $modified_coverage = null; 173 } 174 175 if ($item) { 176 $context = $item->getContext(); 177 if ($context) { 178 $any_context = true; 179 } 180 } else { 181 $context = null; 182 } 183 184 if ($item) { 185 $lines = $item->renderChangesetLines(); 186 } else { 187 $lines = null; 188 } 189 190 $rows[] = array( 191 $context, 192 phutil_tag( 193 'div', 194 array( 195 'style' => $style, 196 ), 197 $display_view), 198 $lines, 199 $coverage, 200 $modified_coverage, 201 $packages, 202 ); 203 204 $classes = array(); 205 206 $have_authority = false; 207 208 if ($item) { 209 $packages = $item->getPackages(); 210 if ($packages) { 211 if (array_intersect_key($packages, $authority)) { 212 $have_authority = true; 213 } 214 } 215 } 216 217 if ($have_authority) { 218 $classes[] = 'highlighted'; 219 } 220 221 if (!$node->getAttribute('important')) { 222 $classes[] = 'diff-toc-low-importance-row'; 223 } 224 225 if ($changeset) { 226 $classes[] = 'diff-toc-changeset-row'; 227 } else { 228 $classes[] = 'diff-toc-no-changeset-row'; 229 } 230 231 $rowc[] = implode(' ', $classes); 232 } 233 234 $table = id(new AphrontTableView($rows)) 235 ->setRowClasses($rowc) 236 ->setClassName('aphront-table-view-compact') 237 ->setHeaders( 238 array( 239 null, 240 pht('Path'), 241 pht('Size'), 242 pht('Coverage (All)'), 243 pht('Coverage (Touched)'), 244 pht('Packages'), 245 )) 246 ->setColumnClasses( 247 array( 248 null, 249 'diff-toc-path wide', 250 'right', 251 'differential-toc-cov', 252 'differential-toc-cov', 253 null, 254 )) 255 ->setColumnVisibility( 256 array( 257 $any_context, 258 true, 259 true, 260 $any_coverage, 261 $any_coverage, 262 $any_packages, 263 )) 264 ->setDeviceVisibility( 265 array( 266 true, 267 true, 268 false, 269 false, 270 false, 271 true, 272 )); 273 274 $anchor = id(new PhabricatorAnchorView()) 275 ->setAnchorName('toc') 276 ->setNavigationMarker(true); 277 278 if ($this->bare) { 279 return $table; 280 } 281 282 $header = id(new PHUIHeaderView()) 283 ->setHeader(pht('Table of Contents')); 284 285 if ($this->header) { 286 $header = $this->header; 287 } 288 289 $box = id(new PHUIObjectBoxView()) 290 ->setHeader($header) 291 ->setBackground($this->background) 292 ->setTable($table) 293 ->appendChild($anchor); 294 295 if ($this->infoView) { 296 $box->setInfoView($this->infoView); 297 } 298 299 return $box; 300 } 301 302 private function newTreeVector($old, $new) { 303 if ($old === null && $new === null) { 304 throw new Exception(pht('Changeset has no path vectors!')); 305 } 306 307 $vector = null; 308 if ($old === null) { 309 $vector = $new; 310 } else if ($new === null) { 311 $vector = $old; 312 } else if ($old === $new) { 313 $vector = $new; 314 } 315 316 if ($vector) { 317 foreach ($vector as $k => $v) { 318 $vector[$k] = $this->newScalarComponent($v); 319 } 320 return $vector; 321 } 322 323 $matrix = id(new PhutilEditDistanceMatrix()) 324 ->setSequences($old, $new) 325 ->setComputeString(true); 326 $edits = $matrix->getEditString(); 327 328 // If the edit sequence contains deletions followed by edits, move 329 // the deletions to the end to left-align the new path. 330 $edits = preg_replace('/(d+)(x+)/', '\2\1', $edits); 331 332 $vector = array(); 333 $length = strlen($edits); 334 335 $old_cursor = 0; 336 $new_cursor = 0; 337 338 for ($ii = 0; $ii < strlen($edits); $ii++) { 339 $c = $edits[$ii]; 340 switch ($c) { 341 case 'i': 342 $vector[] = $this->newPairComponent(null, $new[$new_cursor]); 343 $new_cursor++; 344 break; 345 case 'd': 346 $vector[] = $this->newPairComponent($old[$old_cursor], null); 347 $old_cursor++; 348 break; 349 case 's': 350 case 'x': 351 case 't': 352 $vector[] = $this->newPairComponent( 353 $old[$old_cursor], 354 $new[$new_cursor]); 355 $old_cursor++; 356 $new_cursor++; 357 break; 358 default: 359 throw new Exception(pht('Unknown edit string "%s"!', $c)); 360 } 361 } 362 363 return $vector; 364 } 365 366 private function newScalarComponent($v) { 367 $key = sprintf('path(%s)', $v); 368 369 if (!isset($this->components[$key])) { 370 $this->components[$key] = $v; 371 } 372 373 return $key; 374 } 375 376 private function newPairComponent($u, $v) { 377 if ($u === $v) { 378 return $this->newScalarComponent($u); 379 } 380 381 $key = sprintf('pair(%s > %s)', $u, $v); 382 383 if (!isset($this->components[$key])) { 384 $this->components[$key] = array($u, $v); 385 } 386 387 return $key; 388 } 389 390 private function newComponentView( 391 $icon, 392 array $keys, 393 $old_dir, 394 $new_dir, 395 $item) { 396 397 $is_simple = true; 398 399 $items = array(); 400 foreach ($keys as $key) { 401 $component = $this->components[$key]; 402 403 if (is_array($component)) { 404 $is_simple = false; 405 } else { 406 $component = array( 407 $component, 408 $component, 409 ); 410 } 411 412 $items[] = $component; 413 } 414 415 $move_icon = id(new PHUIIconView()) 416 ->setIcon('fa-angle-double-right pink'); 417 418 $old_row = array( 419 phutil_tag('td', array(), $move_icon), 420 ); 421 $new_row = array( 422 phutil_tag('td', array(), $icon), 423 ); 424 425 $last_old_key = null; 426 $last_new_key = null; 427 428 foreach ($items as $key => $component) { 429 if (!is_array($component)) { 430 $last_old_key = $key; 431 $last_new_key = $key; 432 } else { 433 if ($component[0] !== null) { 434 $last_old_key = $key; 435 } 436 if ($component[1] !== null) { 437 $last_new_key = $key; 438 } 439 } 440 } 441 442 foreach ($items as $key => $component) { 443 if (!is_array($component)) { 444 $old = $component; 445 $new = $component; 446 } else { 447 $old = $component[0]; 448 $new = $component[1]; 449 } 450 451 $old_classes = array(); 452 $new_classes = array(); 453 454 if ($old === $new) { 455 // Do nothing. 456 } else if ($old === null) { 457 $new_classes[] = 'diff-path-component-new'; 458 } else if ($new === null) { 459 $old_classes[] = 'diff-path-component-old'; 460 } else { 461 $old_classes[] = 'diff-path-component-old'; 462 $new_classes[] = 'diff-path-component-new'; 463 } 464 465 if ($old !== null) { 466 if (($key === $last_old_key) && !$old_dir) { 467 // Do nothing. 468 } else { 469 $old = $old.'/'; 470 } 471 } 472 473 if ($new !== null) { 474 if (($key === $last_new_key) && $item) { 475 $new = $item->newLink(); 476 } else if (($key === $last_new_key) && !$new_dir) { 477 // Do nothing. 478 } else { 479 $new = $new.'/'; 480 } 481 } 482 483 $old_row[] = phutil_tag( 484 'td', 485 array(), 486 phutil_tag( 487 'div', 488 array( 489 'class' => implode(' ', $old_classes), 490 ), 491 $old)); 492 $new_row[] = phutil_tag( 493 'td', 494 array(), 495 phutil_tag( 496 'div', 497 array( 498 'class' => implode(' ', $new_classes), 499 ), 500 $new)); 501 } 502 503 $old_row = phutil_tag( 504 'tr', 505 array( 506 'class' => 'diff-path-old', 507 ), 508 $old_row); 509 510 $new_row = phutil_tag( 511 'tr', 512 array( 513 'class' => 'diff-path-new', 514 ), 515 $new_row); 516 517 $rows = array(); 518 $rows[] = $new_row; 519 if (!$is_simple) { 520 $rows[] = $old_row; 521 } 522 523 $body = phutil_tag('tbody', array(), $rows); 524 525 $table = phutil_tag( 526 'table', 527 array( 528 ), 529 $body); 530 531 return $table; 532 } 533 534}