@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 upstream/main 256 lines 5.6 kB view raw
1<?php 2 3final class PhabricatorDocumentEngineBlocks 4 extends Phobject { 5 6 private $lists = array(); 7 private $messages = array(); 8 private $rangeMin; 9 private $rangeMax; 10 private $revealedIndexes; 11 private $layoutAvailableRowCount; 12 13 public function setRange($min, $max) { 14 $this->rangeMin = $min; 15 $this->rangeMax = $max; 16 return $this; 17 } 18 19 public function setRevealedIndexes(array $indexes) { 20 $this->revealedIndexes = $indexes; 21 return $this; 22 } 23 24 public function getLayoutAvailableRowCount() { 25 if ($this->layoutAvailableRowCount === null) { 26 throw new PhutilInvalidStateException('new...Layout'); 27 } 28 29 return $this->layoutAvailableRowCount; 30 } 31 32 public function addMessage($message) { 33 $this->messages[] = $message; 34 return $this; 35 } 36 37 public function getMessages() { 38 return $this->messages; 39 } 40 41 /** 42 * @param ?PhabricatorDocumentRef $ref 43 * @param array<PhabricatorDocumentEngineBlock> $blocks 44 */ 45 public function addBlockList( 46 ?PhabricatorDocumentRef $ref = null, 47 array $blocks = array()) { 48 49 assert_instances_of($blocks, PhabricatorDocumentEngineBlock::class); 50 51 $this->lists[] = array( 52 'ref' => $ref, 53 'blocks' => array_values($blocks), 54 ); 55 56 return $this; 57 } 58 59 public function getDocumentRefs() { 60 return ipull($this->lists, 'ref'); 61 } 62 63 public function newTwoUpLayout() { 64 $rows = array(); 65 $lists = $this->lists; 66 67 if (count($lists) != 2) { 68 $this->layoutAvailableRowCount = 0; 69 return array(); 70 } 71 72 $specs = array(); 73 foreach ($this->lists as $list) { 74 $specs[] = $this->newDiffSpec($list['blocks']); 75 } 76 77 $old_map = $specs[0]['map']; 78 $new_map = $specs[1]['map']; 79 80 $old_list = $specs[0]['list']; 81 $new_list = $specs[1]['list']; 82 83 $changeset = id(new PhabricatorDifferenceEngine()) 84 ->generateChangesetFromFileContent($old_list, $new_list); 85 86 $hunk_parser = id(new DifferentialHunkParser()) 87 ->parseHunksForLineData($changeset->getHunks()) 88 ->reparseHunksForSpecialAttributes(); 89 90 $hunk_parser->generateVisibleBlocksMask(2); 91 $mask = $hunk_parser->getVisibleLinesMask(); 92 93 $old_lines = $hunk_parser->getOldLines(); 94 $new_lines = $hunk_parser->getNewLines(); 95 96 $rows = array(); 97 98 $count = count($old_lines); 99 for ($ii = 0; $ii < $count; $ii++) { 100 $old_line = idx($old_lines, $ii); 101 $new_line = idx($new_lines, $ii); 102 103 $is_visible = !empty($mask[$ii]); 104 105 if ($old_line) { 106 $old_hash = rtrim($old_line['text'], "\n"); 107 if (!strlen($old_hash)) { 108 // This can happen when one of the sources has no blocks. 109 $old_block = null; 110 } else { 111 $old_block = array_shift($old_map[$old_hash]); 112 $old_block 113 ->setDifferenceType($old_line['type']) 114 ->setIsVisible($is_visible); 115 } 116 } else { 117 $old_block = null; 118 } 119 120 if ($new_line) { 121 $new_hash = rtrim($new_line['text'], "\n"); 122 if (!strlen($new_hash)) { 123 $new_block = null; 124 } else { 125 $new_block = array_shift($new_map[$new_hash]); 126 $new_block 127 ->setDifferenceType($new_line['type']) 128 ->setIsVisible($is_visible); 129 } 130 } else { 131 $new_block = null; 132 } 133 134 // If both lists are empty, we may generate a row which has two empty 135 // blocks. 136 if (!$old_block && !$new_block) { 137 continue; 138 } 139 140 $rows[] = array( 141 $old_block, 142 $new_block, 143 ); 144 } 145 146 $this->layoutAvailableRowCount = count($rows); 147 148 $rows = $this->revealIndexes($rows, true); 149 $rows = $this->sliceRows($rows); 150 151 return $rows; 152 } 153 154 public function newOneUpLayout() { 155 $rows = array(); 156 $lists = $this->lists; 157 158 $idx = 0; 159 while (true) { 160 $found_any = false; 161 162 $row = array(); 163 foreach ($lists as $list) { 164 $blocks = $list['blocks']; 165 $cell = idx($blocks, $idx); 166 167 if ($cell !== null) { 168 $found_any = true; 169 } 170 171 if ($cell) { 172 $rows[] = $cell; 173 } 174 } 175 176 if (!$found_any) { 177 break; 178 } 179 180 $idx++; 181 } 182 183 $this->layoutAvailableRowCount = count($rows); 184 185 $rows = $this->revealIndexes($rows, false); 186 $rows = $this->sliceRows($rows); 187 188 return $rows; 189 } 190 191 192 private function newDiffSpec(array $blocks) { 193 $map = array(); 194 $list = array(); 195 196 foreach ($blocks as $block) { 197 $hash = $block->getDifferenceHash(); 198 199 if (!isset($map[$hash])) { 200 $map[$hash] = array(); 201 } 202 $map[$hash][] = $block; 203 204 $list[] = $hash; 205 } 206 207 return array( 208 'map' => $map, 209 'list' => implode("\n", $list)."\n", 210 ); 211 } 212 213 private function sliceRows(array $rows) { 214 $min = $this->rangeMin; 215 $max = $this->rangeMax; 216 217 if ($min === null && $max === null) { 218 return $rows; 219 } 220 221 if ($max === null) { 222 return array_slice($rows, $min, null, true); 223 } 224 225 if ($min === null) { 226 $min = 0; 227 } 228 229 return array_slice($rows, $min, $max - $min, true); 230 } 231 232 private function revealIndexes(array $rows, $is_vector) { 233 if ($this->revealedIndexes === null) { 234 return $rows; 235 } 236 237 foreach ($this->revealedIndexes as $index) { 238 if (!isset($rows[$index])) { 239 continue; 240 } 241 242 if ($is_vector) { 243 foreach ($rows[$index] as $block) { 244 if ($block !== null) { 245 $block->setIsVisible(true); 246 } 247 } 248 } else { 249 $rows[$index]->setIsVisible(true); 250 } 251 } 252 253 return $rows; 254 } 255 256}