@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<?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}